Skip to content

그룹스터디 후기 기능 추가 및 스터디 수정 모달 관련 로직 수정#444

Merged
HA-SEUNG-JEONG merged 17 commits into
mainfrom
haseung/fix-study
Mar 24, 2026
Merged

그룹스터디 후기 기능 추가 및 스터디 수정 모달 관련 로직 수정#444
HA-SEUNG-JEONG merged 17 commits into
mainfrom
haseung/fix-study

Conversation

@HA-SEUNG-JEONG

@HA-SEUNG-JEONG HA-SEUNG-JEONG commented Mar 24, 2026

Copy link
Copy Markdown
Contributor

🌱 연관된 이슈

☘️ 작업 내용

  • 그룹스터디 후기 기능 추가
  • 스터디 수정 모달에서 제출 버튼 비활성화 문제 수정

🍀 참고사항

스크린샷 (선택)

Summary by CodeRabbit

  • New Features

    • 1:1·그룹·멘토 후기 전용 목록·상세·작성 UI(탭 내비게이션, 작성 모달, 완료 모달) 추가
    • 받은 평가 통계·키워드 카드·키워드 상세 모달·평가 항목 집계 추가
    • 자동 후기 알림(미작성 시 팝업) 및 제출 후 축하 애니메이션(콘페티) 추가
    • 별점 입력(0.5 단위) UI, 후기 전개/축소 토글, 페이지네이션, 툴바 기반 검색·정렬·필터 추가
  • Bug Fixes

    • 스터디 폼 편집 모드 시작일 검증 개선 및 관련 폼 동작 안정화

@HA-SEUNG-JEONG HA-SEUNG-JEONG self-assigned this Mar 24, 2026
@vercel

vercel Bot commented Mar 24, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
study-platform-client-dev Error Error Mar 24, 2026 7:05pm

@HA-SEUNG-JEONG HA-SEUNG-JEONG changed the title 그룹스터디 후기 기능 추가 그룹스터디 후기 기능 추가 및 스터디 수정 모달 관련 로직 수정 Mar 24, 2026
@coderabbitai

coderabbitai Bot commented Mar 24, 2026

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 62c02c9c-eb14-4158-9a3e-a7312d320e6f

📥 Commits

Reviewing files that changed from the base of the PR and between 45b0d7c and 0e6b8b2.

📒 Files selected for processing (6)
  • src/app/(service)/(my)/my-study-review/_components/review-page-content.tsx
  • src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx
  • src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_hooks/use-review-statistics.ts
  • src/app/(service)/(my)/my-study-review/mentor/[groupStudyId]/page.tsx
  • src/components/pages/study-list-toolbar.tsx
  • src/hooks/common/use-group-study-review-reminder.ts

📝 Walkthrough

Walkthrough

마이 스터디 리뷰 기능을 새로 추가하고 관련 레이아웃, 모달, 훅, 쿼리, 타입 및 그룹/멘토/1:1 리뷰 페이지와 통계·평가 UI를 도입했습니다. 일부 스터디 리스트 페이지와 그룹 스터디 폼/스키마도 관련 로직으로 함께 변경되었습니다.

Changes

Cohort / File(s) Summary
레이아웃 변경
src/app/(service)/(my)/layout.tsx
중앙 콘텐츠 래퍼를 고정 너비에서 flex 기반 반응형 레이아웃(flex-1, px-400, pt-400, w-full max-w-[780px])으로 조정.
마이 스터디 리뷰 — 공통 컴포넌트/훅/페이지 진입
src/app/(service)/(my)/my-study-review/page.tsx, .../CompletedStudyReviewPage .../study-review-tab-nav.tsx, .../use-expandable-content.tsx, .../review-page-content.tsx
마이 리뷰 메인 페이지를 그룹 경로로 리다이렉트하도록 변경하고, CompletedStudyReviewPage와 탭 네비/확장 훅·리뷰 콘텐츠 컴포넌트 추가.
완료된 스터디 조회·카드 및 역할 섹션
src/app/(service)/(my)/my-study-review/_components/completed-study-review-page.tsx, .../group/_components/member-study-card.tsx, .../StudyRoleSection
완료 스터디 조회, 역할별 분류(리더/참여자), 멤버 스터디 카드와 클릭·모달 트리거 로직 추가.
그룹 리뷰 — 리스트/상세/컴포넌트/훅/페이지
src/app/(service)/(my)/my-study-review/group/page.tsx, group/[groupStudyId]/page.tsx, group/[groupStudyId]/_components/*, group/[groupStudyId]/_hooks/use-review-statistics.ts
그룹 리뷰 리스트·상세 페이지, 통계 훅(useReviewStatistics), Satisfaction/Evaluation 섹션, GroupReviewCard 등 UI와 통계 결합 로직 추가.
멘토 리뷰 페이지
src/app/(service)/(my)/my-study-review/mentor/page.tsx, mentor/[groupStudyId]/page.tsx
멘토스터디 전용 리스트 및 상세 페이지 추가(그룹과 동일한 통계/렌더링 패턴).
1:1 리뷰 페이지·유틸·카드
src/app/(service)/(my)/my-study-review/one-to-one/page.tsx, one-to-one/_components/one-to-one-review-card.tsx, one-to-one/_utils.ts
1:1 리뷰 페이지, 리뷰 카드, 통계 빌드 유틸 추가.
리뷰 쿼리/타입/폼 훅/리마인더
src/hooks/queries/group-study-review-api.ts, src/types/api/group-study-review.types.ts, src/hooks/common/use-group-study-review-form.ts, src/hooks/common/use-group-study-review-reminder.ts
그룹 스터디 리뷰용 쿼리 키·React Query 훅(조회/상세/작성/수정/삭제/작성여부), 타입 정의, 리뷰 폼 훅(검증·제출) 및 완료 후 미작성 자동 알림 훅 추가.
리뷰 모달·완료 모달·별점 UI
src/components/common/modals/group-study-review-modal.tsx, .../study-completion-modal.tsx, .../study-review-modal.tsx, src/components/common/ui/star-rating-input.tsx
그룹 리뷰 작성 모달, 제출 성공 후 축하(confetti) 모달, 기존 StudyReviewModal 업데이트(onSubmitSuccess 등), 반응형 별점 입력 컴포넌트 추가.
그룹 스터디 폼·스키마·편집 모드
src/components/common/modals/group-study-form-modal.tsx, src/components/forms/group-study-form.tsx, src/components/forms/group-study-steps/step1-group.tsx, src/types/schemas/group-study-form.schema.ts
폼 모드(context: create
스터디 리스트 툴바·리스트 페이지 통합
src/components/pages/study-list-toolbar.tsx, src/components/pages/group-study-list-page.tsx, src/components/pages/premium-study-list-page.tsx
헤더/필터/검색/정렬을 StudyListToolbar로 통합하고, 그룹/프리미엄 리스트에 리뷰/완료 모달(리마인더 훅) 통합.
패키지
package.json
애니메이션용 canvas-confetti 및 타입(@types/canvas-confetti) 추가.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client as Client(React)
    participant API as Backend API
    participant DB as Database

    User->>Client: /my-study-review/group 접속
    Client->>API: 완료된 스터디 목록 요청 (useMemberStudyListQuery)
    API->>DB: 사용자 스터디 조회
    DB-->>API: 스터디 목록 반환
    API-->>Client: 스터디 목록 전달

    Client->>API: 각 스터디별 작성여부 병렬 요청 (groupStudyReviewQueryKeys.written)
    API->>DB: 리뷰 작성 여부 조회
    DB-->>API: 작성여부 응답
    API-->>Client: 작성여부 반환

    Client->>Client: 클릭 가능성 판단(리더/이미 작성 등)
    User->>Client: 작성 가능한 스터디 클릭
    Client->>API: 그룹 스터디 상세 및 선택 항목 요청
    API->>DB: 상세/선택항목 조회
    DB-->>API: 상세정보 반환
    API-->>Client: 상세정보 전달

    Client->>Client: GroupStudyReviewModal 표시
    User->>Client: 리뷰 입력 및 제출
    Client->>API: 리뷰 생성 요청 (useCreateGroupStudyReview)
    API->>DB: 리뷰 저장
    DB-->>API: 저장 결과
    API-->>Client: 성공 응답

    Client->>Client: 모달 닫고 StudyCompletionModal 오픈 (confetti)
Loading
sequenceDiagram
    actor User
    participant Client as Client(React)
    participant API as Backend API
    participant DB as Database

    User->>Client: /my-study-review/group/[groupStudyId] 접속
    Client->>API: 그룹 스터디 상세 조회
    API->>DB: 스터디 정보 조회
    DB-->>API: 스터디 정보 반환
    API-->>Client: 상세정보 전달

    Client->>API: 리뷰 목록 조회 (page=1,size=100)
    API->>DB: 리뷰 목록 조회
    DB-->>API: 목록 반환
    API-->>Client: 리뷰 목록 전달

    Client->>Client: 리뷰 ID 추출
    Client->>API: 각 리뷰 상세 병렬 조회 (useQueries)
    API->>DB: 상세 조회(다건)
    DB-->>API: 상세 반환
    API-->>Client: 모든 상세 정보 전달

    Client->>Client: useReviewStatistics로 통계 계산 및 UI 렌더링
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

🐰 탭 사이 폴짝 뛰어들어,
별 하나 반짝, 리뷰 한 줄 툭,
모달에 불꽃 펑펑 터지고,
키워드도 속삭이며 모여,
스터디의 날들이 반짝이네 ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.55% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목이 풀 리퀘스트의 주요 변경사항을 명확하게 요약합니다. 그룹스터디 후기 기능 추가와 스터디 수정 모달 로직 수정이라는 두 가지 주요 작업이 간결하고 구체적으로 표현되어 있습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch haseung/fix-study

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (5)
src/app/(service)/(my)/my-study-review/_components/use-expandable-content.tsx-8-16 (1)

8-16: ⚠️ Potential issue | 🟡 Minor

content 변경 시 showButton 상태가 업데이트되지 않습니다.

useEffect의 의존성 배열이 비어있어 컴포넌트 마운트 시에만 실행됩니다. content가 동적으로 변경되면 버튼 표시 여부가 재계산되지 않아 UI가 올바르게 업데이트되지 않을 수 있습니다.

🐛 의존성 배열에 content 추가 제안
   useEffect(() => {
     if (contentRef.current) {
       const lineHeight =
         parseInt(window.getComputedStyle(contentRef.current).lineHeight, 10) ||
         20;
       const maxHeight = lineHeight * 3;
       setShowButton(contentRef.current.scrollHeight > maxHeight);
     }
-  }, []);
+  }, [content]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/_components/use-expandable-content.tsx
around lines 8 - 16, The effect that calculates whether to show the expand
button (inside useEffect using contentRef, calculating lineHeight and maxHeight
and calling setShowButton) only runs on mount because its dependency array is
empty; update the dependency array to include the dynamic value(s) that should
retrigger recalculation—at minimum include content (and optionally
contentRef.current if needed) so that when content changes the effect reruns and
setShowButton updates accordingly. Ensure the useEffect references remain
correct (contentRef, setShowButton, and any helpers used to compute lineHeight)
so React linter warnings are satisfied.
src/app/(service)/(my)/my-study-review/_components/review-page-content.tsx-145-149 (1)

145-149: ⚠️ Potential issue | 🟡 Minor

onClickfetchNextPage를 직접 전달하는 대신 화살표 함수로 감싸세요.

useInfiniteQuery에서 반환되는 fetchNextPage를 직접 전달하면 클릭 이벤트 객체가 함수의 첫 번째 인자로 전달될 수 있습니다. 프로젝트 코드베이스의 다른 곳(예: one-to-one/page.tsx 라인 65)에서 이미 onClick={() => fetchNextPage()}로 올바르게 구현하고 있으므로 동일한 패턴을 적용해야 합니다.

제안 수정
-            onClick={fetchNextPage}
+            onClick={() => fetchNextPage()}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(service)/(my)/my-study-review/_components/review-page-content.tsx
around lines 145 - 149, The onClick currently passes fetchNextPage directly
which may receive the click event as its first argument; change the handler to
an arrow function so it calls fetchNextPage without passing the event (i.e.,
replace onClick={fetchNextPage} with onClick={() => fetchNextPage()}) on the
button that is rendered when hasNextPage is true to match the pattern used
elsewhere (e.g., one-to-one/page.tsx) and avoid unintended arguments being
forwarded.
src/components/common/modals/group-study-review-modal.tsx-235-254 (1)

235-254: ⚠️ Potential issue | 🟡 Minor

만족도 토글의 선택 상태를 보조기기에 알려주세요.

지금은 시각적으로만 활성 상태가 바뀌어서 스크린리더가 현재 선택을 읽어주지 못합니다. 최소한 aria-pressed={isSelected}를 넣거나 라디오 그룹 semantics로 바꾸는 편이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/modals/group-study-review-modal.tsx` around lines 235 -
254, The visual-only toggle button in group-study-review-modal.tsx needs
accessible state so screen readers announce selection; update the button element
that uses isSelected/onClick/label/emoji to include an ARIA state such as
aria-pressed={isSelected} (or switch to proper radio-group semantics if this is
part of a mutually exclusive set), and ensure the element has an appropriate
role/keyboard behavior if you change semantics; add aria-pressed to the existing
<button> for the quickest fix.
src/components/common/modals/study-completion-modal.tsx-45-74 (1)

45-74: ⚠️ Potential issue | 🟡 Minor

닫힐 때 requestAnimationFrame 루프를 취소하지 않고 있습니다.

캔버스를 제거해도 이미 예약된 프레임은 3초 동안 계속 돌아갑니다. 빠르게 열고 닫으면 불필요한 애니메이션 루프가 겹칠 수 있습니다.

수정 예시
-    const frame = () => {
-      if (Date.now() > end) return;
+    let rafId: number | null = null;
+    let cancelled = false;
+
+    const frame = () => {
+      if (cancelled || Date.now() > end) return;
@@
-      requestAnimationFrame(frame);
+      rafId = requestAnimationFrame(frame);
     };
@@
     return () => {
+      cancelled = true;
+      if (rafId !== null) cancelAnimationFrame(rafId);
       if (canvasRef.current) {
         canvasRef.current.remove();
         canvasRef.current = null;
       }
       confettiRef.current = null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/modals/study-completion-modal.tsx` around lines 45 -
74, The frame animation loop created by function frame uses
requestAnimationFrame but never cancels it on unmount, causing lingering loops;
fix by storing the requestAnimationFrame id (e.g., in confettiRef or a new
rafRef) each time you call requestAnimationFrame(frame), check a cancellation
flag or confettiRef before scheduling the next frame, and call
cancelAnimationFrame(storedId) in the cleanup return block (along with removing
the canvas and clearing refs) so the loop is properly stopped when the modal
unmounts or the canvas is removed.
src/app/(service)/(my)/my-study-review/_components/completed-study-review-page.tsx-91-97 (1)

91-97: ⚠️ Potential issue | 🟡 Minor

memberIdnull일 때 쿼리가 실행될 수 있습니다.

memberId ?? 0으로 0을 전달하면 쿼리가 실행되어 잘못된 데이터를 요청할 수 있습니다. useMemberStudyListQueryenabled 조건을 추가하거나, memberId가 없을 때 early return을 고려해주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/_components/completed-study-review-page.tsx
around lines 91 - 97, The query may run with memberId null because we pass
memberId ?? 0 to useMemberStudyListQuery; update the call to avoid executing
when memberId is falsy by adding an enabled flag (e.g. enabled:
Boolean(memberId)) or by returning early when memberId is null/undefined before
calling useMemberStudyListQuery; refer to useMemberStudyListQuery, memberId, and
completedStudyResponse to locate and gate the query.
🧹 Nitpick comments (14)
src/components/common/ui/star-rating-input.tsx (1)

13-19: roundToNearestHalf 함수의 불필요한 조건 분기

num === 0일 때의 특별 처리가 불필요합니다. Math.round(0 * 2) / 2는 항상 0을 반환하므로, .toFixed(1)을 적용하고 Number()로 변환해도 결과는 동일합니다.

♻️ 간소화된 구현 제안
 const roundToNearestHalf = (num: number) => {
-  if (num === 0) {
-    return Number((Math.round(num * 2) / 2).toFixed(1));
-  }
-
   return Math.round(num * 2) / 2;
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/ui/star-rating-input.tsx` around lines 13 - 19, The
special-case branch in roundToNearestHalf is unnecessary; remove the if (num ===
0) block and simplify the function to always return Math.round(num * 2) / 2 so
zero and other values are handled uniformly; update the function
roundToNearestHalf to a single return expression and ensure callers still expect
a number (no toFixed/Number conversion).
src/app/(service)/(my)/my-study-review/_components/review-page-content.tsx (1)

11-11: 상대 경로 import 대신 @/ alias로 맞춰 주세요.

이 훅도 @/app/(service)/(my)/my-study-review/_components/use-expandable-content처럼 절대 경로로 통일하는 편이 좋겠습니다.

As per coding guidelines, Component path aliases: use @/ prefix mapped to ./src/ (configured in tsconfig.json) for all imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(service)/(my)/my-study-review/_components/review-page-content.tsx
at line 11, Replace the relative import of the hook useExpandableContent (import
{ useExpandableContent } from './use-expandable-content') with the project alias
form that begins with '@/...' so it matches the tsconfig alias convention;
update the import statement to use the alias-prefixed module path (keeping the
same exported symbol useExpandableContent) to make imports consistent with other
components.
src/app/(service)/(my)/my-study-review/group/page.tsx (1)

3-3: 신규 페이지 import도 @/ alias로 맞춰 주세요.

이 경로는 @/app/(service)/(my)/my-study-review/_components/completed-study-review-page로 통일하는 편이 좋겠습니다.

As per coding guidelines, Component path aliases: use @/ prefix mapped to ./src/ (configured in tsconfig.json) for all imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(service)/(my)/my-study-review/group/page.tsx at line 3, The import
in page.tsx uses a relative path for CompletedStudyReviewPage; update the import
to use the project path alias by replacing the relative import with
"@/app/(service)/(my)/my-study-review/_components/completed-study-review-page"
so CompletedStudyReviewPage is imported via the `@/` alias (adjust the import
statement in page.tsx to reference that module).
src/app/(service)/(my)/my-study-review/_components/study-review-tab-nav.tsx (1)

19-48: 활성 탭 상태를 보조기기에 노출해 주세요.

지금은 활성 여부가 스타일로만 표현돼서 스크린 리더가 현재 페이지를 알기 어렵습니다. active 항목에 현재 페이지를 나타내는 속성(예: aria-current="page")을 추가해 두는 편이 좋겠습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(service)/(my)/my-study-review/_components/study-review-tab-nav.tsx
around lines 19 - 48, The three tab buttons in the StudyReviewTabNav component
(the buttons that call router.push(...) for '/my-study-review/one-to-one',
'/my-study-review/group', '/my-study-review/mentor' and use cn(tabClassName,
isActive(...) && pathClassName)) rely only on visual styling to show the active
state; update each button to include an accessibility attribute when active
(e.g., add aria-current="page" when isActive('/my-study-review/...') is true) so
screen readers can detect the current page—use the existing isActive(...) check
for each button to conditionally set aria-current.
src/app/(service)/(my)/my-study-review/mentor/page.tsx (1)

3-3: 여기도 @/ alias import로 통일해 주세요.

../_components/completed-study-review-page보다 절대 경로 import가 유지보수에 더 안정적입니다.

As per coding guidelines, Component path aliases: use @/ prefix mapped to ./src/ (configured in tsconfig.json) for all imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(service)/(my)/my-study-review/mentor/page.tsx at line 3, The import
for CompletedStudyReviewPage currently uses a relative path; update the import
to use the project alias (`@/`) mapped to ./src/ to follow the Component path
aliases guideline. Replace the line importing CompletedStudyReviewPage (import
CompletedStudyReviewPage from '../_components/completed-study-review-page') with
an alias import that starts with "@/..." pointing to the same module, ensuring
the symbol CompletedStudyReviewPage and the module name
completed-study-review-page remain unchanged so other references continue to
work. Verify TypeScript resolves the alias (tsconfig paths) and run a quick
build/type-check to confirm no import errors.
src/app/(service)/(my)/my-study-review/one-to-one/_components/one-to-one-review-card.tsx (1)

7-7: 상대 경로 import 대신 @/ alias로 통일해 주세요.

../../_components/use-expandable-content보다 @/app/(service)/(my)/my-study-review/_components/use-expandable-content가 이동/리팩터링에 더 안전합니다.

As per coding guidelines, Component path aliases: use @/ prefix mapped to ./src/ (configured in tsconfig.json) for all imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/one-to-one/_components/one-to-one-review-card.tsx
at line 7, Replace the relative import of useExpandableContent with the project
alias: update the import statement that currently references
'../../_components/use-expandable-content' in one-to-one-review-card.tsx to use
the alias module
'@/app/(service)/(my)/my-study-review/_components/use-expandable-content' and
keep the imported symbol name useExpandableContent unchanged so tooling and
refactors resolve correctly.
src/components/forms/group-study-form.tsx (1)

24-25: context와 hook을 전용 모듈로 분리하세요.

step1-group.tsx가 이 파일에서 useMode를 가져오면서 순환 의존이 발생합니다: group-study-form.tsxstep1-group.tsxgroup-study-form.tsx. useModeuseClassification을 별도 파일(예: src/contexts/group-study-mode.ts)로 추출하면 이 의존성을 제거하고 초기화 순서와 HMR 안정성을 개선할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/forms/group-study-form.tsx` around lines 24 - 25, Extract the
ModeContext and useMode (and useClassification if present) into a dedicated
module (e.g., a new src/contexts/group-study-mode.ts) so they are no longer
defined inside group-study-form.tsx; move the createContext<'create' |
'edit'>('create') declaration and the export const useMode hook into that new
file, export both the context and hook, then update group-study-form.tsx and
step1-group.tsx to import ModeContext/useMode (and useClassification) from the
new module; ensure you remove the original declarations from
group-study-form.tsx to break the circular dependency and keep the same exported
identifiers (ModeContext, useMode, useClassification) so callers need minimal
changes.
src/components/common/modals/group-study-review-modal.tsx (1)

56-56: 금지된 arbitrary Tailwind 값이 여러 곳에 들어가 있습니다.

max-w-[360px], h-[120px], h-[52px], w-[52px], text-[28px]는 저장소 규칙상 토큰 클래스로 치환해야 합니다.

As per coding guidelines Forbidden: Tailwind arbitrary values (e.g., p-[4px], w-[320px]). Use only project custom design tokens.

Also applies to: 174-174, 245-250

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/modals/group-study-review-modal.tsx` at line 56,
Replace all forbidden arbitrary Tailwind utilities (max-w-[360px], h-[120px],
h-[52px], w-[52px], text-[28px]) with the project's design-token classes; locate
the usages in the JSX (e.g., the Modal.Title element and the avatar/box elements
that currently use those bracketed utilities) and swap each arbitrary value for
the equivalent token class (or add a new design token if none exists), updating
the className strings accordingly so only approved token classes remain.
src/app/(service)/(my)/my-study-review/group/_components/evaluation-section.tsx (1)

30-30: min-h-[280px]는 토큰 클래스로 바꿔주세요.

저장소 규칙상 arbitrary Tailwind 값은 허용되지 않습니다.

As per coding guidelines Forbidden: Tailwind arbitrary values (e.g., p-[4px], w-[320px]). Use only project custom design tokens.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/group/_components/evaluation-section.tsx
at line 30, The div in evaluation-section.tsx uses an arbitrary Tailwind class
min-h-[280px] which violates the project's rule against arbitrary values;
replace that class with the appropriate project design token class (the
component's root div with className "rounded-100 border-border-subtle
min-h-[280px] border p-200") — find the matching min-height token from the
design token token set (or use the existing project token naming convention,
e.g., min-h-XXX) and update the className to use that token instead of
min-h-[280px].
src/components/common/modals/study-completion-modal.tsx (1)

53-60: 테마 토큰 대신 하드코딩된 값이 들어가 있습니다.

여기의 hex 색상들과 text-[64px]는 저장소 규칙과 맞지 않습니다. CSS 변수나 프로젝트 토큰을 읽어서 쓰는 쪽이 테마 변경에도 안전합니다.

As per coding guidelines Forbidden: Hard-coded colors and spacing values. Use only @theme inline tokens from global.css and custom Tailwind classes.

Also applies to: 88-88

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/modals/study-completion-modal.tsx` around lines 53 -
60, The confetti color hexes and the hardcoded `text-[64px]` in the
StudyCompletionModal are forbidden; locate the confetti invocations (calls to
fire(...) with the colors: [...] arrays) and replace the hard-coded hex values
with the theme color tokens or CSS variables exposed in global.css (use the same
token names your project uses for primary/accent/grape/amber/green), and replace
`text-[64px]` with the appropriate project Tailwind/custom class (or an `@theme`
inline token class) that maps to the 64px size; ensure you read tokens from the
global theme (or use var(--your-token)) rather than literals so theme changes
apply consistently.
src/hooks/queries/group-study-review-api.ts (1)

130-136: 순차적 캐시 무효화를 병렬로 처리하면 성능이 개선됩니다.

현재 await를 순차적으로 호출하고 있어 두 무효화 작업이 직렬로 실행됩니다. Promise.all로 병렬 처리하면 약간의 성능 향상이 가능합니다.

♻️ 병렬 무효화 제안
 onSuccess: async (_, variables) => {
-  await queryClient.invalidateQueries({
-    queryKey: groupStudyReviewQueryKeys.lists(),
-  });
-  await queryClient.invalidateQueries({
-    queryKey: groupStudyReviewQueryKeys.written(variables.groupStudyId),
-  });
+  await Promise.all([
+    queryClient.invalidateQueries({
+      queryKey: groupStudyReviewQueryKeys.lists(),
+    }),
+    queryClient.invalidateQueries({
+      queryKey: groupStudyReviewQueryKeys.written(variables.groupStudyId),
+    }),
+  ]);
 },

Also applies to: 154-161

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/queries/group-study-review-api.ts` around lines 130 - 136, In the
onSuccess callback where you currently await queryClient.invalidateQueries twice
in sequence (using groupStudyReviewQueryKeys.lists() and
groupStudyReviewQueryKeys.written(variables.groupStudyId)), run both
invalidations in parallel with Promise.all to avoid serial waits; update the
onSuccess handler that calls queryClient.invalidateQueries with
groupStudyReviewQueryKeys.lists() and groupStudyReviewQueryKeys.written(...) to
invoke them inside Promise.all, and make the same change for the similar block
referenced around lines 154-161.
src/app/(service)/(my)/my-study-review/_components/completed-study-review-page.tsx (3)

8-8: 다이나믹 임포트 패턴이 일관되지 않습니다.

StudyCompletionModalStudyReviewModaldynamic()으로 지연 로딩하지만, GroupStudyReviewModal은 직접 임포트합니다. 모달의 크기와 사용 빈도가 유사하다면 일관된 패턴을 적용하는 것이 좋습니다.

♻️ 일관된 다이나믹 임포트 적용
-import GroupStudyReviewModal from '@/components/common/modals/group-study-review-modal';

+const GroupStudyReviewModal = dynamic(
+  () => import('@/components/common/modals/group-study-review-modal'),
+  { ssr: false },
+);

Also applies to: 17-25

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/_components/completed-study-review-page.tsx
at line 8, The import of GroupStudyReviewModal is inconsistent with the
lazy/dynamic pattern used for StudyCompletionModal and StudyReviewModal; replace
the direct import of GroupStudyReviewModal with a dynamic import using the same
dynamic() helper used for StudyCompletionModal and StudyReviewModal so all
modals are loaded lazily and consistently; update any references in the
component (e.g., CompletedStudyReviewPage render logic) to use the dynamically
imported GroupStudyReviewModal identifier and ensure props and default export
usage remain unchanged.

58-65: studyId는 필수 필드이므로 nullish coalescing이 불필요합니다.

MemberStudyItem 타입에서 studyId: number는 필수 필드입니다 (context snippet 1 참조). study.studyId ?? index 대신 study.studyId만 사용해도 됩니다.

♻️ 간소화 제안
 {studies.map((study, index) => (
   <MemberStudyCard
-    key={study.studyId ?? index}
+    key={study.studyId}
     study={study}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/_components/completed-study-review-page.tsx
around lines 58 - 65, The key for the mapped MemberStudyCard should use the
required studyId instead of a fallback; update the studies.map callback so the
key is study.studyId (remove the nullish coalescing `?? index`) — locate the
mapping that renders <MemberStudyCard ... /> in the studies.map, and replace the
key expression `study.studyId ?? index` with `study.studyId` to rely on the
required field defined on MemberStudyItem.

110-125: 쿼리 키 불일치로 캐시 무효화가 누락됩니다.

이 컴포넌트는 ['study-review', 'written', studyType, study.studyId] 쿼리 키를 사용하지만, useCreateGroupStudyReviewgroupStudyReviewQueryKeys.written(groupStudyId)를 무효화합니다 (src/hooks/queries/group-study-review-api.ts 참조). 이로 인해 후기 작성 후에도 이 컴포넌트의 eligibility 체크가 갱신되지 않습니다.

submittedStudyIds 상태로 세션 내에서는 해결되지만, 다른 탭이나 새로고침 시에는 캐시 불일치가 발생할 수 있습니다.

♻️ 권장 수정: 기존 쿼리 키 패턴 사용
+import { groupStudyReviewQueryKeys } from '@/hooks/queries/group-study-review-api';

 const writtenResults = useQueries({
   queries: participantStudies.map((study) => ({
-    queryKey: ['study-review', 'written', studyType, study.studyId],
+    queryKey: studyType === 'ONE_ON_ONE_STUDY'
+      ? ['study-review', 'written', studyType, study.studyId]
+      : groupStudyReviewQueryKeys.written(study.studyId),
     queryFn: async () => {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/_components/completed-study-review-page.tsx
around lines 110 - 125, The hardcoded queryKey array in the writtenResults
useQueries call causes cache mismatches with the invalidation performed by
useCreateGroupStudyReview; change the queryKey logic to use the shared query-key
generators instead of a custom array — i.e., for group studies use
groupStudyReviewQueryKeys.written(study.studyId) and for one-on-one studies use
the corresponding one-on-one review query key generator (import the existing key
helpers used elsewhere) so that invalidation from useCreateGroupStudyReview
actually refreshes writtenResults; update imports to bring in
groupStudyReviewQueryKeys (and the one-on-one key helper) and replace the
queryKey assignment inside the map accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/app/`(service)/(my)/layout.tsx:
- Around line 20-21: The div in layout.tsx is using Tailwind arbitrary values
pb-[100px] and max-w-[780px], which violates the design-token rule; replace
pb-[100px] with an existing spacing token class (e.g., pb-400) or add a new
spacing token in global.css and use that token's class, and replace
max-w-[780px] with a custom max-width utility defined in your design tokens
(e.g., add a max-w-<token> in global.css and use that class) so both pb and
max-w use project-approved tokens instead of arbitrary values.

In
`@src/app/`(service)/(my)/my-study-review/group/_components/evaluation-section.tsx:
- Around line 88-93: The variable goodItems is inferred as any[] which causes
implicit any in the reduce callback; explicitly type goodItems (and similarly
disappointedItems if needed) as GroupStudyReviewStatisticsItem[] so the reduce
parameters are correctly typed. Locate the declaration of goodItems (and
disappointedItems) in evaluation-section.tsx and replace the inferred-typed
initialization with either a type assertion or an explicit type annotation using
GroupStudyReviewStatisticsItem[] so goodTotalCount's reduce callback no longer
uses implicit any.

In
`@src/app/`(service)/(my)/my-study-review/group/_components/satisfaction-section.tsx:
- Around line 71-86: The two arbitrary Tailwind width utilities (sm:w-[88px] and
sm:w-[96px]) in the satisfaction-section.tsx JSX violate the project guideline;
replace them with the project's custom spacing tokens defined in global.css
(e.g., the --spacing-* tokens) or add new tokens if no existing size matches.
Locate the small-screen width uses on the div containing "좋았어요" and the trailing
span that shows "{goodPercent}% ({goodCount}/{totalCount})" and swap the inline
arbitrary widths for the appropriate token-backed utility classes (or create new
--spacing-XXX tokens and corresponding utility classes) so sizes come from the
token system rather than hardcoded pixel values.

In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_hooks/use-review-statistics.ts:
- Around line 73-76: The filter callback for building allItems is causing TS7006
because the parameter item lacks an explicit type; update the lambda used in
reviewDetails.flatMap to annotate item with the correct element type from
selectableReviewItems (e.g., SelectableReviewItem or the actual interface used
for review items), such as (item: SelectableReviewItem | undefined) => ..., and
keep the same guard (check item?.id) so the filter still excludes
falsy/undefined items; locate the allItems declaration and the
selectableReviewItems usages to apply this typed parameter.

In `@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/page.tsx:
- Around line 33-37: The current call to useGetGroupStudyReviews hardcodes page:
1 and size: 100 so reviewsData (and derived list/detail/statistics) is truncated
when totalElements > 100; change to a paginated or infinite-load approach:
replace the single static call with a state-driven paginator (e.g., page, size
state and a loadMore or onPageChange handler) that calls useGetGroupStudyReviews
with the current page and merges/appends results, or implement a looped fetch to
retrieve all pages using totalElements/totalPages returned by the hook; update
any consumers that read reviewsData to use the aggregated list (or current page)
and ensure groupStudyId is still passed through to useGetGroupStudyReviews.
- Around line 46-54: The code filters out in-flight or failed detail queries
(detailQueries -> reviewDetails) and immediately passes the truncated list into
useReviewStatistics, causing statistics to shrink and permanently exclude failed
items; instead detect whether all detailQueries are settled (useQueries results'
isLoading/isError/isFetched flags) and only call useReviewStatistics with the
full set of detail results once every query is settled, otherwise render/return
a loading or partial state (or call useReviewStatistics with the original
reviews list) so that loading and error states are separated from computed
statistics; update the logic around detailQueries, reviewDetails and the
useReviewStatistics invocation (and consider using reviewIds and
groupStudyReviewDetailQueryOptions to rebuild options) so failures are not
silently dropped during aggregation.
- Around line 39-44: The reviewsData access uses wrong property names and lets
types fall back to any; update code to use the correct response properties and
an explicit type: replace reviewsData?.content with reviewsData?.items and
reviewsData?.totalElements with reviewsData?.totalCount, and declare reviews
with the explicit type GroupStudyExperienceReviewListItem[] so reviewIds
mapping/filtering (and downstream rendering callbacks) have proper types; adjust
any references to reviews/reviewIds/totalElements accordingly to match these
corrected symbols.

In `@src/app/`(service)/(my)/my-study-review/mentor/[groupStudyId]/page.tsx:
- Around line 33-37: The reviews fetch currently hardcodes a single-page request
(useGetGroupStudyReviews called with page: 1, size: 100), causing listings,
details and stats to be truncated when totalElements > 100; change the logic to
fully load all reviews — either by iterating pages until you have fetched
totalElements (call useGetGroupStudyReviews repeatedly or enhance the hook to
accept a size based on the initial response) or by first fetching metadata
(totalElements) and then requesting with size=totalElements; update the code
around the useGetGroupStudyReviews call and any consumers of reviewsData (e.g.,
listings, detail and stats renderers) to use the combined/full result instead of
only the first page.
- Around line 46-54: 현재 reviewDetails에서 미완료/실패 항목을 제외하고 즉시 useReviewStatistics를
호출해 부분 집계가 로딩 중이거나 실패한 항목을 반영하지 못하고 있습니다; 변경할 것: detailQueries 배열의
상태(detailQueries.every(q => q.isSuccess), any isLoading, any isError)를 검사해 모든 상세
쿼리가 성공일 때만 reviewDetails (타입: GroupStudyExperienceReviewDetail)를 생성하고
useReviewStatistics를 호출하도록 하고, 하나라도 로딩이면 통계 대신 별도 로딩 UI를, 하나라도 에러면 에러 상태(또는 실패
항목에 대한 처리)를 표시하도록 변경하세요; 관련 식별자: detailQueries, reviewDetails,
useReviewStatistics, GroupStudyExperienceReviewDetail,
reviewIds.map(groupStudyReviewDetailQueryOptions).
- Around line 12-14: The imports for EvaluationSection, GroupReviewCard, and
SatisfactionSection in page.tsx point to a non-existent sibling under
[groupStudyId]; update each import to use the project alias (start with "@/")
and the actual shared folder (e.g., "@/app/(service)/(my)/group/_components/…")
so they resolve to the shared group/_components location; modify the import
statements that reference '../../group/[groupStudyId]/_components/...' to use
the '@/...' paths for EvaluationSection, GroupReviewCard, and
SatisfactionSection.

In `@src/app/`(service)/(my)/my-study-review/one-to-one/page.tsx:
- Around line 16-36: The page currently falls back to empty arrays/0 for
positiveData, negativeData and reviewData which makes loading/errors look like
"0 reviews"; update the component to surface loading and error states from
useUserPositiveKeywordsQuery, useMyNegativeKeywordsQuery and
useMyReviewsInfinityQuery before computing statistics or rendering
EvaluationSection and the reviews empty state: check each query's
isLoading/isFetching and isError/error and render inline loaders or error
messages (per guidelines: inline error first, optional Toast secondary) instead
of proceeding to buildEvaluationStatistics or using reviews/totalCount
fallbacks; only compute statistics and render EvaluationSection/reviews list
when the relevant queries have succeeded.

In `@src/components/common/modals/group-study-form-modal.tsx`:
- Around line 320-335: The edit modal still forces the start-date min to
"tomorrow" because GroupStudyForm doesn't forward the edit mode or a minDate to
the Step1OpenGroupStudy component; update GroupStudyForm (the component rendered
with methods={createMethods} and methods={editMethods}) to accept and forward a
mode prop (or compute/passthrough a minDate) into Step1OpenGroupStudy, and
update step1-group.tsx (Step1OpenGroupStudy) to use the passed mode/minDate (or
the existing default startDate when mode === 'edit') when computing the date
input's min instead of always using tomorrow so the edit flow can select
today/past existing start dates.

In `@src/components/common/modals/study-completion-modal.tsx`:
- Line 3: The build fails because StudyCompletionModal imports canvas-confetti
(import confetti from 'canvas-confetti';) but the dependency is missing from
package.json; add canvas-confetti to dependencies in package.json (pick a stable
version or run npm install --save canvas-confetti or yarn add canvas-confetti to
update package.json and lockfile), commit the updated package.json and lockfile,
and rebuild so the StudyCompletionModal can be bundled correctly.

In `@src/components/forms/group-study-steps/step1-group.tsx`:
- Around line 69-72: The current startDateMin computation forces min to
today/tomorrow even in edit mode, which invalidates an existing past startDate;
update the logic that computes startDateMin (the variable using mode,
formatKoreaYMD, addDays, getKoreaDate) so that in 'create' mode it remains
tomorrow, but in edit mode it uses the earlier of the current date and the
preloaded startDate (i.e., if the form's existing startDate is before today, use
that existing value as min) — locate where startDateMin is calculated and
replace the fixed-today branch with a comparison between getKoreaDate() (or
addDays for create) and the form's current startDate value so the existing past
date remains selectable.

In `@src/hooks/common/use-group-study-review-form.ts`:
- Around line 116-140: The handleSubmit function calls createReview with only
onSuccess; add an onError handler to the createReview call so failures surface
to the user: inside the createReview options object, implement onError to call
showToast with an error message (and optionally log the error) and ensure any UI
state cleanup (e.g., closing modal or leaving form state) is handled
appropriately; update the createReview invocation in
use-group-study-review-form.ts (the createReview call inside handleSubmit) to
include this onError callback.

---

Minor comments:
In
`@src/app/`(service)/(my)/my-study-review/_components/completed-study-review-page.tsx:
- Around line 91-97: The query may run with memberId null because we pass
memberId ?? 0 to useMemberStudyListQuery; update the call to avoid executing
when memberId is falsy by adding an enabled flag (e.g. enabled:
Boolean(memberId)) or by returning early when memberId is null/undefined before
calling useMemberStudyListQuery; refer to useMemberStudyListQuery, memberId, and
completedStudyResponse to locate and gate the query.

In `@src/app/`(service)/(my)/my-study-review/_components/review-page-content.tsx:
- Around line 145-149: The onClick currently passes fetchNextPage directly which
may receive the click event as its first argument; change the handler to an
arrow function so it calls fetchNextPage without passing the event (i.e.,
replace onClick={fetchNextPage} with onClick={() => fetchNextPage()}) on the
button that is rendered when hasNextPage is true to match the pattern used
elsewhere (e.g., one-to-one/page.tsx) and avoid unintended arguments being
forwarded.

In
`@src/app/`(service)/(my)/my-study-review/_components/use-expandable-content.tsx:
- Around line 8-16: The effect that calculates whether to show the expand button
(inside useEffect using contentRef, calculating lineHeight and maxHeight and
calling setShowButton) only runs on mount because its dependency array is empty;
update the dependency array to include the dynamic value(s) that should
retrigger recalculation—at minimum include content (and optionally
contentRef.current if needed) so that when content changes the effect reruns and
setShowButton updates accordingly. Ensure the useEffect references remain
correct (contentRef, setShowButton, and any helpers used to compute lineHeight)
so React linter warnings are satisfied.

In `@src/components/common/modals/group-study-review-modal.tsx`:
- Around line 235-254: The visual-only toggle button in
group-study-review-modal.tsx needs accessible state so screen readers announce
selection; update the button element that uses isSelected/onClick/label/emoji to
include an ARIA state such as aria-pressed={isSelected} (or switch to proper
radio-group semantics if this is part of a mutually exclusive set), and ensure
the element has an appropriate role/keyboard behavior if you change semantics;
add aria-pressed to the existing <button> for the quickest fix.

In `@src/components/common/modals/study-completion-modal.tsx`:
- Around line 45-74: The frame animation loop created by function frame uses
requestAnimationFrame but never cancels it on unmount, causing lingering loops;
fix by storing the requestAnimationFrame id (e.g., in confettiRef or a new
rafRef) each time you call requestAnimationFrame(frame), check a cancellation
flag or confettiRef before scheduling the next frame, and call
cancelAnimationFrame(storedId) in the cleanup return block (along with removing
the canvas and clearing refs) so the loop is properly stopped when the modal
unmounts or the canvas is removed.

---

Nitpick comments:
In
`@src/app/`(service)/(my)/my-study-review/_components/completed-study-review-page.tsx:
- Line 8: The import of GroupStudyReviewModal is inconsistent with the
lazy/dynamic pattern used for StudyCompletionModal and StudyReviewModal; replace
the direct import of GroupStudyReviewModal with a dynamic import using the same
dynamic() helper used for StudyCompletionModal and StudyReviewModal so all
modals are loaded lazily and consistently; update any references in the
component (e.g., CompletedStudyReviewPage render logic) to use the dynamically
imported GroupStudyReviewModal identifier and ensure props and default export
usage remain unchanged.
- Around line 58-65: The key for the mapped MemberStudyCard should use the
required studyId instead of a fallback; update the studies.map callback so the
key is study.studyId (remove the nullish coalescing `?? index`) — locate the
mapping that renders <MemberStudyCard ... /> in the studies.map, and replace the
key expression `study.studyId ?? index` with `study.studyId` to rely on the
required field defined on MemberStudyItem.
- Around line 110-125: The hardcoded queryKey array in the writtenResults
useQueries call causes cache mismatches with the invalidation performed by
useCreateGroupStudyReview; change the queryKey logic to use the shared query-key
generators instead of a custom array — i.e., for group studies use
groupStudyReviewQueryKeys.written(study.studyId) and for one-on-one studies use
the corresponding one-on-one review query key generator (import the existing key
helpers used elsewhere) so that invalidation from useCreateGroupStudyReview
actually refreshes writtenResults; update imports to bring in
groupStudyReviewQueryKeys (and the one-on-one key helper) and replace the
queryKey assignment inside the map accordingly.

In `@src/app/`(service)/(my)/my-study-review/_components/review-page-content.tsx:
- Line 11: Replace the relative import of the hook useExpandableContent (import
{ useExpandableContent } from './use-expandable-content') with the project alias
form that begins with '@/...' so it matches the tsconfig alias convention;
update the import statement to use the alias-prefixed module path (keeping the
same exported symbol useExpandableContent) to make imports consistent with other
components.

In `@src/app/`(service)/(my)/my-study-review/_components/study-review-tab-nav.tsx:
- Around line 19-48: The three tab buttons in the StudyReviewTabNav component
(the buttons that call router.push(...) for '/my-study-review/one-to-one',
'/my-study-review/group', '/my-study-review/mentor' and use cn(tabClassName,
isActive(...) && pathClassName)) rely only on visual styling to show the active
state; update each button to include an accessibility attribute when active
(e.g., add aria-current="page" when isActive('/my-study-review/...') is true) so
screen readers can detect the current page—use the existing isActive(...) check
for each button to conditionally set aria-current.

In
`@src/app/`(service)/(my)/my-study-review/group/_components/evaluation-section.tsx:
- Line 30: The div in evaluation-section.tsx uses an arbitrary Tailwind class
min-h-[280px] which violates the project's rule against arbitrary values;
replace that class with the appropriate project design token class (the
component's root div with className "rounded-100 border-border-subtle
min-h-[280px] border p-200") — find the matching min-height token from the
design token token set (or use the existing project token naming convention,
e.g., min-h-XXX) and update the className to use that token instead of
min-h-[280px].

In `@src/app/`(service)/(my)/my-study-review/group/page.tsx:
- Line 3: The import in page.tsx uses a relative path for
CompletedStudyReviewPage; update the import to use the project path alias by
replacing the relative import with
"@/app/(service)/(my)/my-study-review/_components/completed-study-review-page"
so CompletedStudyReviewPage is imported via the `@/` alias (adjust the import
statement in page.tsx to reference that module).

In `@src/app/`(service)/(my)/my-study-review/mentor/page.tsx:
- Line 3: The import for CompletedStudyReviewPage currently uses a relative
path; update the import to use the project alias (`@/`) mapped to ./src/ to follow
the Component path aliases guideline. Replace the line importing
CompletedStudyReviewPage (import CompletedStudyReviewPage from
'../_components/completed-study-review-page') with an alias import that starts
with "@/..." pointing to the same module, ensuring the symbol
CompletedStudyReviewPage and the module name completed-study-review-page remain
unchanged so other references continue to work. Verify TypeScript resolves the
alias (tsconfig paths) and run a quick build/type-check to confirm no import
errors.

In
`@src/app/`(service)/(my)/my-study-review/one-to-one/_components/one-to-one-review-card.tsx:
- Line 7: Replace the relative import of useExpandableContent with the project
alias: update the import statement that currently references
'../../_components/use-expandable-content' in one-to-one-review-card.tsx to use
the alias module
'@/app/(service)/(my)/my-study-review/_components/use-expandable-content' and
keep the imported symbol name useExpandableContent unchanged so tooling and
refactors resolve correctly.

In `@src/components/common/modals/group-study-review-modal.tsx`:
- Line 56: Replace all forbidden arbitrary Tailwind utilities (max-w-[360px],
h-[120px], h-[52px], w-[52px], text-[28px]) with the project's design-token
classes; locate the usages in the JSX (e.g., the Modal.Title element and the
avatar/box elements that currently use those bracketed utilities) and swap each
arbitrary value for the equivalent token class (or add a new design token if
none exists), updating the className strings accordingly so only approved token
classes remain.

In `@src/components/common/modals/study-completion-modal.tsx`:
- Around line 53-60: The confetti color hexes and the hardcoded `text-[64px]` in
the StudyCompletionModal are forbidden; locate the confetti invocations (calls
to fire(...) with the colors: [...] arrays) and replace the hard-coded hex
values with the theme color tokens or CSS variables exposed in global.css (use
the same token names your project uses for primary/accent/grape/amber/green),
and replace `text-[64px]` with the appropriate project Tailwind/custom class (or
an `@theme` inline token class) that maps to the 64px size; ensure you read tokens
from the global theme (or use var(--your-token)) rather than literals so theme
changes apply consistently.

In `@src/components/common/ui/star-rating-input.tsx`:
- Around line 13-19: The special-case branch in roundToNearestHalf is
unnecessary; remove the if (num === 0) block and simplify the function to always
return Math.round(num * 2) / 2 so zero and other values are handled uniformly;
update the function roundToNearestHalf to a single return expression and ensure
callers still expect a number (no toFixed/Number conversion).

In `@src/components/forms/group-study-form.tsx`:
- Around line 24-25: Extract the ModeContext and useMode (and useClassification
if present) into a dedicated module (e.g., a new
src/contexts/group-study-mode.ts) so they are no longer defined inside
group-study-form.tsx; move the createContext<'create' | 'edit'>('create')
declaration and the export const useMode hook into that new file, export both
the context and hook, then update group-study-form.tsx and step1-group.tsx to
import ModeContext/useMode (and useClassification) from the new module; ensure
you remove the original declarations from group-study-form.tsx to break the
circular dependency and keep the same exported identifiers (ModeContext,
useMode, useClassification) so callers need minimal changes.

In `@src/hooks/queries/group-study-review-api.ts`:
- Around line 130-136: In the onSuccess callback where you currently await
queryClient.invalidateQueries twice in sequence (using
groupStudyReviewQueryKeys.lists() and
groupStudyReviewQueryKeys.written(variables.groupStudyId)), run both
invalidations in parallel with Promise.all to avoid serial waits; update the
onSuccess handler that calls queryClient.invalidateQueries with
groupStudyReviewQueryKeys.lists() and groupStudyReviewQueryKeys.written(...) to
invoke them inside Promise.all, and make the same change for the similar block
referenced around lines 154-161.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5fbacaae-0bc0-482c-b8b7-bafbb92d6b70

📥 Commits

Reviewing files that changed from the base of the PR and between f73468f and a0b89e7.

📒 Files selected for processing (28)
  • src/app/(service)/(my)/layout.tsx
  • src/app/(service)/(my)/my-study-review/_components/completed-study-review-page.tsx
  • src/app/(service)/(my)/my-study-review/_components/review-page-content.tsx
  • src/app/(service)/(my)/my-study-review/_components/study-review-tab-nav.tsx
  • src/app/(service)/(my)/my-study-review/_components/use-expandable-content.tsx
  • src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_hooks/use-review-statistics.ts
  • src/app/(service)/(my)/my-study-review/group/[groupStudyId]/page.tsx
  • src/app/(service)/(my)/my-study-review/group/_components/evaluation-section.tsx
  • src/app/(service)/(my)/my-study-review/group/_components/group-review-card.tsx
  • src/app/(service)/(my)/my-study-review/group/_components/member-study-card.tsx
  • src/app/(service)/(my)/my-study-review/group/_components/satisfaction-section.tsx
  • src/app/(service)/(my)/my-study-review/group/page.tsx
  • src/app/(service)/(my)/my-study-review/mentor/[groupStudyId]/page.tsx
  • src/app/(service)/(my)/my-study-review/mentor/page.tsx
  • src/app/(service)/(my)/my-study-review/one-to-one/_components/one-to-one-review-card.tsx
  • src/app/(service)/(my)/my-study-review/one-to-one/_utils.ts
  • src/app/(service)/(my)/my-study-review/one-to-one/page.tsx
  • src/app/(service)/(my)/my-study-review/page.tsx
  • src/components/common/modals/group-study-form-modal.tsx
  • src/components/common/modals/group-study-review-modal.tsx
  • src/components/common/modals/study-completion-modal.tsx
  • src/components/common/modals/study-review-modal.tsx
  • src/components/common/ui/star-rating-input.tsx
  • src/components/forms/group-study-form.tsx
  • src/components/forms/group-study-steps/step1-group.tsx
  • src/hooks/common/use-group-study-review-form.ts
  • src/hooks/queries/group-study-review-api.ts
  • src/types/schemas/group-study-form.schema.ts

Comment on lines +20 to +21
<div className="flex-1 px-400 pt-400 pb-[100px] lg:px-0 lg:pt-500">
<div className="w-full max-w-[780px] mx-auto">{children}</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check available spacing and width tokens in global.css

# Search for spacing tokens
rg -n "spacing|padding|--p-|--pb-" src/app/global.css

# Search for max-width tokens
rg -n "max-w|--max-w" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 3699


🏁 Script executed:

#!/bin/bash
# Check tailwind config to understand token naming and scale
fd -t f 'tailwind.config' . | head -5

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

#!/bin/bash
# Look for any other pb (padding-bottom) token definitions
rg -n "pb-|padding-bottom" src/app/global.css | head -20

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

#!/bin/bash
# Check if pb-400, pb-500 tokens exist
rg -n "@utility pb-" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

#!/bin/bash
# Get the full context of lines 20-21 in the file
sed -n '15,25p' src/app/\(service\)/\(my\)/layout.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 364


🏁 Script executed:

#!/bin/bash
# Search more broadly for tailwind config files
find . -maxdepth 2 -name "tailwind.config.*" -o -name "tsconfig.json" | grep -E "(tailwind|ts)" | head -10

Repository: code-zero-to-one/study-platform-client

Length of output: 97


🏁 Script executed:

#!/bin/bash
# Check if there's a CSS or config that might define pb-400, pb-500, etc. as utilities
rg -n "@utility.*p" src/app/global.css | grep -i "pb\|padding" | head -20

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

#!/bin/bash
# Look at the complete spacing/utility definition section in global.css
sed -n '374,700p' src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 6007


🏁 Script executed:

#!/bin/bash
# Check if there's a pb-400 or pb-500 utility defined elsewhere
rg -n "@utility pb-" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

#!/bin/bash
# Look for any dynamic utility pattern or theme extension
grep -n "pb-\|padding-bottom" src/app/global.css | head -20

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

#!/bin/bash
# Check if there are any other max-w definitions that might match 780px
rg -n "max-w-|@utility.*w" src/app/global.css | grep "max-w"

Repository: code-zero-to-one/study-platform-client

Length of output: 167


🏁 Script executed:

#!/bin/bash
# Verify if this is an admin page (exempt from custom token rules)
head -5 src/app/\(service\)/\(my\)/layout.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 317


Tailwind 임의 값 사용이 코딩 가이드라인을 위반합니다.

pb-[100px]max-w-[780px]는 임의 값(arbitrary values)을 사용하고 있습니다. 프로젝트 커스텀 디자인 토큰만 사용해야 합니다. 예를 들어, px-400pt-400, pt-500은 올바르게 global.css에 정의된 spacing 토큰을 사용하고 있습니다. 비슷하게 pb-max-w-도 정의된 토큰 또는 새로운 utility 클래스를 사용해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(service)/(my)/layout.tsx around lines 20 - 21, The div in
layout.tsx is using Tailwind arbitrary values pb-[100px] and max-w-[780px],
which violates the design-token rule; replace pb-[100px] with an existing
spacing token class (e.g., pb-400) or add a new spacing token in global.css and
use that token's class, and replace max-w-[780px] with a custom max-width
utility defined in your design tokens (e.g., add a max-w-<token> in global.css
and use that class) so both pb and max-w use project-approved tokens instead of
arbitrary values.

Comment on lines +25 to +27
const [expanded, setExpanded] = useState(false);
const visible = expanded ? items : items.slice(0, DEFAULT_SHOW_COUNT);
const hasMore = items.length > DEFAULT_SHOW_COUNT;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

showToggle이 없는 카드가 6번째 항목부터 영구히 잘립니다.

visible이 항상 앞 5개만 보여주도록 계산돼서, 지금처럼 두 번째 카드에 showToggle을 넘기지 않으면 disappointedItems가 5개를 넘어도 나머지 항목을 볼 방법이 없습니다.

수정 예시
-  const visible = expanded ? items : items.slice(0, DEFAULT_SHOW_COUNT);
+  const visible =
+    !showToggle || expanded ? items : items.slice(0, DEFAULT_SHOW_COUNT);

Also applies to: 114-117

Comment on lines +16 to +36
const { data: positiveData } = useUserPositiveKeywordsQuery({});
const { data: negativeData } = useMyNegativeKeywordsQuery({});
const {
data: reviewData,
fetchNextPage,
hasNextPage,
} = useMyReviewsInfinityQuery();

const statistics = buildEvaluationStatistics(
positiveData?.keywords ?? [],
negativeData?.keywords ?? [],
);

const reviews = reviewData?.reviews ?? [];
const totalCount = reviewData?.totalCount ?? 0;

return (
<div className="flex flex-col gap-400">
<StudyReviewTabNav />

<EvaluationSection statistics={statistics} />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

로딩/실패 상태가 실제 빈 후기 상태로 보입니다.

지금은 세 쿼리 중 하나라도 아직 로딩 중이거나 실패하면 []/0 폴백 때문에 바로 후기 0건과 빈 상태 문구가 노출됩니다. 사용자는 데이터가 없는 건지, 요청이 실패한 건지 구분할 수 없습니다. 로딩/에러 상태를 먼저 분리해서 인라인으로 보여주는 쪽이 안전합니다.

As per coding guidelines For recoverable failures in UI: use inline error display first, Toast as secondary.

Also applies to: 49-58

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(service)/(my)/my-study-review/one-to-one/page.tsx around lines 16 -
36, The page currently falls back to empty arrays/0 for positiveData,
negativeData and reviewData which makes loading/errors look like "0 reviews";
update the component to surface loading and error states from
useUserPositiveKeywordsQuery, useMyNegativeKeywordsQuery and
useMyReviewsInfinityQuery before computing statistics or rendering
EvaluationSection and the reviews empty state: check each query's
isLoading/isFetching and isError/error and render inline loaders or error
messages (per guidelines: inline error first, optional Toast secondary) instead
of proceeding to buildEvaluationStatistics or using reviews/totalCount
fallbacks; only compute statistics and render EvaluationSection/reviews list
when the relevant queries have succeeded.

Comment on lines 320 to +335
<GroupStudyForm
methods={createMethods}
onSubmit={handleSubmitForm}
mode="create"
/>
)}
{mode === 'edit' && isGroupStudyLoading && (
<Modal.Body className="font-designer-16m text-text-subtle py-800 text-center">
<Modal.Body className="font-designer-16m text-text-subtle py-700 text-center">
스터디 정보를 불러오는 중입니다...
</Modal.Body>
)}
{mode === 'edit' && !isGroupStudyLoading && editDefaultValues && (
<GroupStudyForm
methods={editMethods}
onSubmit={handleSubmitForm}
mode="edit"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

mode prop만 넘겨서는 수정 모드 시작일 버그가 아직 남습니다.

이 파일에서 mode="edit"를 내려줘도, 실제 폼 단계가 그 값을 소비하지 않으면 달력 min은 그대로 "내일"에 묶입니다. 그러면 스키마는 허용해도 수정 모달 UI에서는 오늘/기존 과거 시작일을 여전히 선택할 수 없습니다.

Based on learnings step1-group.tsx always sets the date input min prop to tomorrow regardless of create/edit mode, and GroupStudyForm does not pass a mode or minDate prop to Step1OpenGroupStudy.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/modals/group-study-form-modal.tsx` around lines 320 -
335, The edit modal still forces the start-date min to "tomorrow" because
GroupStudyForm doesn't forward the edit mode or a minDate to the
Step1OpenGroupStudy component; update GroupStudyForm (the component rendered
with methods={createMethods} and methods={editMethods}) to accept and forward a
mode prop (or compute/passthrough a minDate) into Step1OpenGroupStudy, and
update step1-group.tsx (Step1OpenGroupStudy) to use the passed mode/minDate (or
the existing default startDate when mode === 'edit') when computing the date
input's min instead of always using tomorrow so the edit flow can select
today/past existing start dates.

Comment thread src/components/common/modals/study-completion-modal.tsx
Comment on lines +69 to +72
const startDateMin =
mode === 'create'
? formatKoreaYMD(addDays(getKoreaDate(), 1))
: formatKoreaYMD(getKoreaDate());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

편집 모드에서 과거 시작일은 아직 선택 불가입니다.

mode !== 'create'일 때 min을 오늘로 고정하면, 이미 시작된 스터디를 수정할 때 기존 startDate가 오늘보다 과거인 경우 브라우저 date input이 invalid 상태가 됩니다. 편집 모드에서는 현재 값이 과거면 그 값을 min으로 써서 “기존 값 유지” 시나리오를 열어 둬야 합니다.

🗓️ 제안 수정
+  const today = formatKoreaYMD(getKoreaDate());
+  const currentStartDate = watch('startDate');
   const startDateMin =
     mode === 'create'
-      ? formatKoreaYMD(addDays(getKoreaDate(), 1))
-      : formatKoreaYMD(getKoreaDate());
+      ? formatKoreaYMD(addDays(getKoreaDate(), 1))
+      : currentStartDate && currentStartDate < today
+        ? currentStartDate
+        : today;
Based on learnings, `step1-group.tsx` always set the date input `min` to tomorrow regardless of mode, so edit mode must keep preloaded dates selectable too.

Also applies to: 248-248

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/forms/group-study-steps/step1-group.tsx` around lines 69 - 72,
The current startDateMin computation forces min to today/tomorrow even in edit
mode, which invalidates an existing past startDate; update the logic that
computes startDateMin (the variable using mode, formatKoreaYMD, addDays,
getKoreaDate) so that in 'create' mode it remains tomorrow, but in edit mode it
uses the earlier of the current date and the preloaded startDate (i.e., if the
form's existing startDate is before today, use that existing value as min) —
locate where startDateMin is calculated and replace the fixed-today branch with
a comparison between getKoreaDate() (or addDays for create) and the form's
current startDate value so the existing past date remains selectable.

Comment on lines +116 to +140
const handleSubmit = () => {
setHasAttemptedSubmit(true);
if (!isFormValid || !form.satisfaction) return;

const { satisfaction, selectedCodes, content, rating } = form;

createReview(
{
groupStudyId,
request: {
satisfaction,
selectableReviewItemCodes: selectedCodes,
content,
rating: Math.round(rating), // 반별점(0.5 단위) → 백엔드 정수 변환
},
},
{
onSuccess: () => {
showToast('후기가 작성되었습니다.', 'success');
onClose();
onSubmitSuccess?.();
},
},
);
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

에러 처리가 누락되어 실패 시 사용자에게 피드백이 없습니다.

createReview 뮤테이션에 onSuccess만 정의되어 있고 onError가 없습니다. 코딩 가이드라인에 따르면 뮤테이션 콜백에서 onError로 에러 토스트를 표시해야 합니다. 후기 작성 실패 시 사용자가 문제를 인지하지 못할 수 있습니다.

🐛 에러 처리 추가 제안
+import { analyzeError } from '@/utils/error-handler';

 createReview(
   {
     groupStudyId,
     request: {
       satisfaction,
       selectableReviewItemCodes: selectedCodes,
       content,
       rating: Math.round(rating),
     },
   },
   {
     onSuccess: () => {
       showToast('후기가 작성되었습니다.', 'success');
       onClose();
       onSubmitSuccess?.();
     },
+    onError: (error) => {
+      const errorInfo = analyzeError(error);
+      showToast(errorInfo.userMessage, 'error');
+    },
   },
 );

As per coding guidelines: "For useMutation hooks, use onSuccess for success-only actions, onError for error handling."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/common/use-group-study-review-form.ts` around lines 116 - 140, The
handleSubmit function calls createReview with only onSuccess; add an onError
handler to the createReview call so failures surface to the user: inside the
createReview options object, implement onError to call showToast with an error
message (and optionally log the error) and ensure any UI state cleanup (e.g.,
closing modal or leaving form state) is handled appropriately; update the
createReview invocation in use-group-study-review-form.ts (the createReview call
inside handleSubmit) to include this onError callback.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (6)
src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx (1)

30-30: 코딩 가이드라인 위반: 임의 값 사용

min-h-[280px]는 Tailwind 임의 값입니다. 프로젝트 가이드라인에 따라 커스텀 디자인 토큰만 사용해야 합니다.

♻️ 수정 제안

프로젝트의 spacing 토큰을 확인하여 적절한 값으로 대체하거나, 필요시 global.css에 새로운 토큰을 추가하세요.

-    <div className="rounded-100 border-border-subtle min-h-[280px] border p-200">
+    <div className="rounded-100 border-border-subtle min-h-3500 border p-200">

(실제 토큰 값은 프로젝트의 디자인 시스템에 맞게 조정 필요)

As per coding guidelines: Forbidden: Tailwind arbitrary values (e.g., p-[4px], w-[320px]). Use only project custom design tokens.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx
at line 30, The div in EvaluationSection component uses a Tailwind arbitrary
value `min-h-[280px]`, violating the project's design-token-only rule; replace
that arbitrary class with the appropriate project spacing token class (e.g., the
component's existing spacing token such as a min-h token from your design
system) on the element in evaluation-section.tsx (the div with className
including rounded-100 border-border-subtle min-h-[280px] border p-200), and if
no suitable token exists add a new min-height token in global.css (naming
consistent with other tokens) then update the className to use that token.
src/app/(service)/(my)/my-study-review/group/[groupStudyId]/page.tsx (1)

12-15: Import 순서 정리 필요

린터 경고에 따르면 GroupReviewCard import가 SatisfactionSection import보다 앞에 와야 합니다.

♻️ 수정 제안
 import EvaluationSection from './_components/evaluation-section';
+import GroupReviewCard from './_components/group-review-card';
 import SatisfactionSection from './_components/satisfaction-section';
 import { useReviewStatistics } from './_hooks/use-review-statistics';
-import GroupReviewCard from './_components/group-review-card';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/page.tsx around
lines 12 - 15, Import order is incorrect: move the GroupReviewCard import so it
appears before SatisfactionSection to satisfy the linter. Open the imports at
the top of page.tsx and reorder the lines so GroupReviewCard is imported prior
to SatisfactionSection (keeping EvaluationSection and useReviewStatistics
positions as needed), then save and re-run the linter/formatter to confirm the
warning is resolved.
src/app/(service)/(my)/my-study-review/mentor/[groupStudyId]/page.tsx (1)

13-17: 상대 경로 대신 @/ alias 사용 권장

코딩 가이드라인에 따라 모든 import에 @/ prefix를 사용해야 합니다. 또한 린터 경고에 따르면 import 순서도 정리가 필요합니다.

♻️ 수정 제안
-import { useReviewStatistics } from '../../group/[groupStudyId]/_hooks/use-review-statistics';
-
-import EvaluationSection from '../../group/[groupStudyId]/_components/evaluation-section';
-import SatisfactionSection from '../../group/[groupStudyId]/_components/satisfaction-section';
-import GroupReviewCard from '../../group/[groupStudyId]/_components/group-review-card';
+import EvaluationSection from '@/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section';
+import GroupReviewCard from '@/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/group-review-card';
+import SatisfactionSection from '@/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/satisfaction-section';
+import { useReviewStatistics } from '@/app/(service)/(my)/my-study-review/group/[groupStudyId]/_hooks/use-review-statistics';

As per coding guidelines: Component path aliases: use @/ prefix mapped to ./src/ (configured in tsconfig.json) for all imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/`(service)/(my)/my-study-review/mentor/[groupStudyId]/page.tsx around
lines 13 - 17, The imports in page.tsx use relative paths instead of the project
alias and violate import ordering rules; update the four imports
(useReviewStatistics, EvaluationSection, SatisfactionSection, GroupReviewCard)
to use the "@/..." alias that maps to ./src/, and reorder them to satisfy the
linter (group external modules, then "@/..." imports, alphabetize or follow your
repo's import-order rules) so the linter warnings go away.
src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/satisfaction-section.tsx (1)

71-71: 코딩 가이드라인 위반: 임의 값 사용

sm:w-[88px]sm:w-[96px] (lines 71, 83, 89, 101)는 Tailwind 임의 값입니다. 프로젝트 디자인 토큰을 사용해야 합니다.

♻️ 수정 제안

프로젝트의 width 토큰으로 대체하거나, 반복적으로 사용되는 값이라면 global.css에 커스텀 토큰을 추가하세요.

-              <div className="flex items-center gap-75 sm:w-[88px] sm:shrink-0">
+              <div className="flex items-center gap-75 sm:w-1100 sm:shrink-0">
...
-              <span className="font-designer-14m text-text-subtle shrink-0 text-left tabular-nums sm:w-[96px] sm:text-right">
+              <span className="font-designer-14m text-text-subtle shrink-0 text-left tabular-nums sm:w-1200 sm:text-right">

(실제 토큰 값은 프로젝트 디자인 시스템 확인 필요)

As per coding guidelines: Forbidden: Tailwind arbitrary values (e.g., p-[4px], w-[320px]). Use only project custom design tokens.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_components/satisfaction-section.tsx
at line 71, Replace the Tailwind arbitrary width values in the
satisfaction-section component with your project's design tokens: locate the div
that contains the class string "flex items-center gap-75 sm:w-[88px]
sm:shrink-0" (and the other occurrences using sm:w-[96px]) in
src/app/.../satisfaction-section.tsx, remove the arbitrary
sm:w-[88px]/sm:w-[96px] values and swap them for the appropriate project width
token class (e.g., sm:w-{your-token}) or a named utility; if the required width
token does not exist, add a CSS variable/utility in global.css (e.g.,
--width-<name> and a .w-<name> helper) and then use that token/helper in the
component classNames instead of the arbitrary bracket syntax.
src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/group-review-card.tsx (1)

7-7: @/ 경로 alias 사용 권장

코딩 가이드라인에 따라 모든 import에 @/ prefix를 사용해야 합니다.

♻️ 수정 제안
-import { useExpandableContent } from '../../../_components/use-expandable-content';
+import { useExpandableContent } from '@/app/(service)/(my)/my-study-review/_components/use-expandable-content';

As per coding guidelines: Component path aliases: use @/ prefix mapped to ./src/ (configured in tsconfig.json) for all imports.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_components/group-review-card.tsx
at line 7, Replace the relative import for useExpandableContent in
group-review-card.tsx with the project path-alias form: locate the import of
useExpandableContent in the file (symbol: useExpandableContent) and change the
module specifier to use the '@/...' alias that maps to ./src/ per tsconfig
(e.g., replace '../../../_components/use-expandable-content' with the
corresponding '@/...' path to the same module), ensuring the import uses the
alias style required by the coding guidelines.
src/components/common/modals/group-study-review-modal.tsx (1)

115-116: Object.keys(...) as ReviewSatisfaction[] 단언은 가드 패턴으로 바꿔 주세요.

Line 115의 강제 단언은 런타임 안전성을 잃습니다. 백엔드 enum-like 문자열 처리 규칙에 맞게 in 가드(또는 안전한 키 배열 상수)로 순회하는 방식이 더 안전합니다.

As per coding guidelines For enum-like string types from backend, use in operator guard with fallback instead of simple as type assertion.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/modals/group-study-review-modal.tsx` around lines 115 -
116, Replace the unsafe type assertion "(Object.keys(SATISFACTION_CONFIG) as
ReviewSatisfaction[])" with a runtime-safe guard: iterate over a validated keys
array or filter Object.keys(...) using an "in" check against SATISFACTION_CONFIG
(or use a constant array of allowed ReviewSatisfaction keys), then cast only
after the guard; update the map call (the expression iterating
SATISFACTION_CONFIG) to use that guarded keys list so each "key" is guaranteed
to be a valid ReviewSatisfaction at runtime.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/components/common/modals/group-study-review-modal.tsx`:
- Line 58: The span currently renders `basicInfo?.startDate` and
`basicInfo?.endDate` directly, which can show "undefined ~ undefined"; update
the rendering in group-study-review-modal.tsx to provide fallbacks when those
fields are missing (e.g., use nullish coalescing or conditional rendering for
`basicInfo?.startDate` and `basicInfo?.endDate` and replace undefined with a
user-friendly placeholder like '-' or '미정'), ensuring the displayed string never
contains "undefined".
- Around line 29-32: Extract the inline object shapes into named interfaces and
use them instead of anonymous types: for SATISFACTION_CONFIG create an interface
(e.g., SatisfactionConfigItem { label: string; emoji: string }) and change the
type from Record<ReviewSatisfaction, { label: string; emoji: string }> to
Record<ReviewSatisfaction, SatisfactionConfigItem>; do the same for the other
inline object shapes found around the other mentioned blocks (lines 80-85 and
227-232) by declaring appropriately named interfaces and replacing the inline
object literal/type annotations with those interface names to follow the repo
coding guideline.
- Line 55: Replace all Tailwind arbitrary value classes in this component with
the project's custom design tokens: change Modal.Title's class from
max-w-[360px] to max-w-360; replace the element using h-[120px] with the closest
permitted height token (e.g., h-200) or discuss the correct token if none
exists; and update the small avatar/icon block where h-[52px], w-[52px],
text-[28px] are used to h-650, w-650 and a font token like font-designer-28r/m/b
respectively (remove the color `text-` prefix when replacing the font size).
Locate these occurrences in GroupStudyReviewModal (look for Modal.Title and the
elements with h-[120px], h-[52px], w-[52px], text-[28px]) and swap each
arbitrary class for the corresponding custom utility token.

---

Nitpick comments:
In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx:
- Line 30: The div in EvaluationSection component uses a Tailwind arbitrary
value `min-h-[280px]`, violating the project's design-token-only rule; replace
that arbitrary class with the appropriate project spacing token class (e.g., the
component's existing spacing token such as a min-h token from your design
system) on the element in evaluation-section.tsx (the div with className
including rounded-100 border-border-subtle min-h-[280px] border p-200), and if
no suitable token exists add a new min-height token in global.css (naming
consistent with other tokens) then update the className to use that token.

In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_components/group-review-card.tsx:
- Line 7: Replace the relative import for useExpandableContent in
group-review-card.tsx with the project path-alias form: locate the import of
useExpandableContent in the file (symbol: useExpandableContent) and change the
module specifier to use the '@/...' alias that maps to ./src/ per tsconfig
(e.g., replace '../../../_components/use-expandable-content' with the
corresponding '@/...' path to the same module), ensuring the import uses the
alias style required by the coding guidelines.

In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_components/satisfaction-section.tsx:
- Line 71: Replace the Tailwind arbitrary width values in the
satisfaction-section component with your project's design tokens: locate the div
that contains the class string "flex items-center gap-75 sm:w-[88px]
sm:shrink-0" (and the other occurrences using sm:w-[96px]) in
src/app/.../satisfaction-section.tsx, remove the arbitrary
sm:w-[88px]/sm:w-[96px] values and swap them for the appropriate project width
token class (e.g., sm:w-{your-token}) or a named utility; if the required width
token does not exist, add a CSS variable/utility in global.css (e.g.,
--width-<name> and a .w-<name> helper) and then use that token/helper in the
component classNames instead of the arbitrary bracket syntax.

In `@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/page.tsx:
- Around line 12-15: Import order is incorrect: move the GroupReviewCard import
so it appears before SatisfactionSection to satisfy the linter. Open the imports
at the top of page.tsx and reorder the lines so GroupReviewCard is imported
prior to SatisfactionSection (keeping EvaluationSection and useReviewStatistics
positions as needed), then save and re-run the linter/formatter to confirm the
warning is resolved.

In `@src/app/`(service)/(my)/my-study-review/mentor/[groupStudyId]/page.tsx:
- Around line 13-17: The imports in page.tsx use relative paths instead of the
project alias and violate import ordering rules; update the four imports
(useReviewStatistics, EvaluationSection, SatisfactionSection, GroupReviewCard)
to use the "@/..." alias that maps to ./src/, and reorder them to satisfy the
linter (group external modules, then "@/..." imports, alphabetize or follow your
repo's import-order rules) so the linter warnings go away.

In `@src/components/common/modals/group-study-review-modal.tsx`:
- Around line 115-116: Replace the unsafe type assertion
"(Object.keys(SATISFACTION_CONFIG) as ReviewSatisfaction[])" with a runtime-safe
guard: iterate over a validated keys array or filter Object.keys(...) using an
"in" check against SATISFACTION_CONFIG (or use a constant array of allowed
ReviewSatisfaction keys), then cast only after the guard; update the map call
(the expression iterating SATISFACTION_CONFIG) to use that guarded keys list so
each "key" is guaranteed to be a valid ReviewSatisfaction at runtime.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8e653a45-475d-41e7-b42f-47e6800b5834

📥 Commits

Reviewing files that changed from the base of the PR and between a0b89e7 and dae80cb.

📒 Files selected for processing (8)
  • src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx
  • src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/group-review-card.tsx
  • src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/satisfaction-section.tsx
  • src/app/(service)/(my)/my-study-review/group/[groupStudyId]/page.tsx
  • src/app/(service)/(my)/my-study-review/mentor/[groupStudyId]/page.tsx
  • src/app/(service)/(my)/my-study-review/one-to-one/page.tsx
  • src/components/common/modals/group-study-review-modal.tsx
  • src/types/api/group-study-review.types.ts
✅ Files skipped from review due to trivial changes (2)
  • src/types/api/group-study-review.types.ts
  • src/app/(service)/(my)/my-study-review/one-to-one/page.tsx

Comment on lines +29 to +32
const SATISFACTION_CONFIG: Record<
ReviewSatisfaction,
{ label: string; emoji: string }
> = {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, verify the file exists and get its structure
wc -l src/components/common/modals/group-study-review-modal.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 143


🏁 Script executed:

# Read the specific lines mentioned in the review comment
sed -n '29,32p' src/components/common/modals/group-study-review-modal.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 179


🏁 Script executed:

# Read lines 80-85
sed -n '80,85p' src/components/common/modals/group-study-review-modal.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 187


🏁 Script executed:

# Read lines 227-232
sed -n '227,232p' src/components/common/modals/group-study-review-modal.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 171


객체 shape 정의 시 interface 선언으로 분리해 주세요.

Lines 29-32, 80-85, 227-232에서 객체 구조를 인라인으로 정의하고 있습니다. 이 레포의 코딩 가이드라인에 따르면 객체 구조는 interface로 선언되어야 합니다.

♻️ 제안 코드
+interface SatisfactionConfigItem {
+  label: string;
+  emoji: string;
+}
+
+interface GroupStudyReviewFormProps {
+  open: boolean;
+  groupStudyId: number;
+  onClose: () => void;
+  onSubmitSuccess?: () => void;
+}
+
+interface SatisfactionButtonProps {
+  label: string;
+  isSelected: boolean;
+  onClick: () => void;
+  emoji: string;
+}
+
-const SATISFACTION_CONFIG: Record<
-  ReviewSatisfaction,
-  { label: string; emoji: string }
-> = {
+const SATISFACTION_CONFIG: Record<ReviewSatisfaction, SatisfactionConfigItem> = {
   GOOD: { label: '좋았어요', emoji: '\u{1F60A}' },
   DISAPPOINTED: { label: '아쉬웠어요', emoji: '\u{1F605}' },
 };
 
-function GroupStudyReviewForm({
+function GroupStudyReviewForm({
   open,
   groupStudyId,
   onClose,
   onSubmitSuccess,
-}: {
-  open: boolean;
-  groupStudyId: number;
-  onClose: () => void;
-  onSubmitSuccess?: () => void;
-}) {
+}: GroupStudyReviewFormProps) {
 
-function SatisfactionButton({
+function SatisfactionButton({
   label,
   isSelected,
   onClick,
   emoji,
-}: {
-  label: string;
-  isSelected: boolean;
-  onClick: () => void;
-  emoji: string;
-}) {
+}: SatisfactionButtonProps) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/modals/group-study-review-modal.tsx` around lines 29 -
32, Extract the inline object shapes into named interfaces and use them instead
of anonymous types: for SATISFACTION_CONFIG create an interface (e.g.,
SatisfactionConfigItem { label: string; emoji: string }) and change the type
from Record<ReviewSatisfaction, { label: string; emoji: string }> to
Record<ReviewSatisfaction, SatisfactionConfigItem>; do the same for the other
inline object shapes found around the other mentioned blocks (lines 80-85 and
227-232) by declaring appropriately named interfaces and replacing the inline
object literal/type annotations with those interface names to follow the repo
coding guideline.

>
<Modal.Header variant="form">
<div className="flex flex-col gap-50">
<Modal.Title className="font-designer-16b text-text-default truncate max-w-[360px]">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/components/common/modals/group-study-review-modal.tsx | head -60 | tail -10

Repository: code-zero-to-one/study-platform-client

Length of output: 633


🏁 Script executed:

cat -n src/components/common/modals/group-study-review-modal.tsx | sed -n '50,60p'

Repository: code-zero-to-one/study-platform-client

Length of output: 696


🏁 Script executed:

cat -n src/components/common/modals/group-study-review-modal.tsx | sed -n '170,180p'

Repository: code-zero-to-one/study-platform-client

Length of output: 526


🏁 Script executed:

cat -n src/components/common/modals/group-study-review-modal.tsx | sed -n '240,250p'

Repository: code-zero-to-one/study-platform-client

Length of output: 505


🏁 Script executed:

# Check if global.css exists and what tokens are available
head -100 src/app/global.css | grep -E "(--.*:|@theme)"

Repository: code-zero-to-one/study-platform-client

Length of output: 853


🏁 Script executed:

# Check if there are sizing/spacing token definitions in global.css
rg -A 2 "(--spacing|--height|--width|--size|--radius)" src/app/global.css | head -40

Repository: code-zero-to-one/study-platform-client

Length of output: 844


🏁 Script executed:

# Check all custom token definitions in global.css
cat src/app/global.css | grep "^[[:space:]]*--" | head -50

Repository: code-zero-to-one/study-platform-client

Length of output: 1496


🏁 Script executed:

# Check if there are Tailwind custom classes defined (e.g., w-400, h-100, etc.)
rg -E "^\s+\.[wh]-\[" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 132


🏁 Script executed:

# Look for sizing scale in global.css more broadly
sed -n '/@theme/,/^}/p' src/app/global.css | head -100

Repository: code-zero-to-one/study-platform-client

Length of output: 2654


🏁 Script executed:

# Check if there are custom Tailwind utility classes defined in tailwind.config or elsewhere
find src -name "tailwind.config.*" -o -name "*.css" | xargs grep -l "extend\|@layer utilities" 2>/dev/null

Repository: code-zero-to-one/study-platform-client

Length of output: 100


🏁 Script executed:

# Check tailwind.config for extend section
find . -maxdepth 3 -name "tailwind.config.*" -type f -exec cat {} \;

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

# Look for any size/dimension token definitions beyond spacing
grep -E "(--size|--width|--height|--font-size)" src/app/global.css | head -30

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

# Check tailwind.config.ts or .js for extend section with width/height/fontSize
cat tailwind.config.ts 2>/dev/null || cat tailwind.config.js 2>/dev/null || echo "No tailwind config found"

Repository: code-zero-to-one/study-platform-client

Length of output: 106


🏁 Script executed:

# Search for custom class definitions that match w-, h-, text- patterns
rg "w-\d+|h-\d+|text-\d+" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 1669


🏁 Script executed:

# Check all `@layer` definitions in global.css
grep -A 5 "@layer" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 685


🏁 Script executed:

# Check if spacing tokens are used for width/height via Tailwind mapping
grep -E "(width|height|fontSize)" tailwind.config.ts tailwind.config.js 2>/dev/null || rg "extend.*:" src -t ts | head -20

Repository: code-zero-to-one/study-platform-client

Length of output: 335


🏁 Script executed:

# Get the full definitions of the custom utilities to see their actual values
grep -A 1 "@utility" src/app/global.css | head -40

Repository: code-zero-to-one/study-platform-client

Length of output: 629


🏁 Script executed:

# Check what w-360, h-200, w-320, h-650 etc. map to in pixels
grep -B 1 -A 3 "@utility w-360\|@utility w-320\|@utility h-200\|@utility h-650\|@utility h-500px" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 270


🏁 Script executed:

# Check for font-size utilities and h-120
grep -A 1 "@utility.*text-\|@utility.*font-\|@utility.*h-1" src/app/global.css | grep -E "(`@utility`|font-size|height)" | head -30

Repository: code-zero-to-one/study-platform-client

Length of output: 801


🏁 Script executed:

# Get full list of all custom utilities defined
grep "@utility" src/app/global.css | sed 's/@utility //g' | sed 's/ {//'

Repository: code-zero-to-one/study-platform-client

Length of output: 1264


🏁 Script executed:

# Check for max-width utilities and h-120 or similar
grep -E "@utility max-w|@utility h-1" src/app/global.css | head -20

Repository: code-zero-to-one/study-platform-client

Length of output: 155


🏁 Script executed:

# Check the exact pixels for all height utilities starting with h-
grep -B 1 "height:" src/app/global.css | grep -E "@utility h-|height:"

Repository: code-zero-to-one/study-platform-client

Length of output: 2773


🏁 Script executed:

# Check max-w utilities with their pixel values
grep -A 1 "@utility max-w-" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 223


🏁 Script executed:

# Check for h-120 and other h-1xx utilities
rg "@utility h-1[0-9]{2}" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

# Check spacing scale - maybe 120 can be built from spacing
grep -E "spacing-.*120|spacing-.*130" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

# Check if text-28 or similar font utilities exist
grep -E "@utility text-|font-size.*28" src/app/global.css | head -20

Repository: code-zero-to-one/study-platform-client

Length of output: 241


🏁 Script executed:

# Find the exact definition of h-650 and w-650 to understand naming
grep -A 1 "@utility h-650\|@utility w-650" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 149


🏁 Script executed:

# Check if there are other h-* utilities with pixel values around 120
grep -E "@utility h-[0-9]+" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 134


🏁 Script executed:

# Check spacing tokens - maybe h-120 should be built from spacing scale
grep "spacing-.*:.*120px" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

# Verify the font-designer-28 utilities exist
grep "font-designer-28" src/app/global.css

Repository: code-zero-to-one/study-platform-client

Length of output: 237


Tailwind 임의값을 프로젝트 커스텀 토큰으로 교체해 주세요.

아래 임의값들이 현재 프로젝트 규칙에서 금지되어 있습니다. 커스텀 유틸리티 클래스로 교체하세요:

  • Line 55: max-w-[360px]max-w-360
  • Line 173: h-[120px] → 대응하는 커스텀 토큰이 없으므로 가장 가까운 높이 유틸리티(h-200 또는 다른 옵션) 검토 필요
  • Line 245:
    • h-[52px]h-650
    • w-[52px]w-650
    • text-[28px]font-designer-28r (또는 28m, 28b), text- 프리픽스는 색상용이므로 수정 필요
코딩 가이드라인 Forbidden: Tailwind arbitrary values (e.g., `p-[4px]`, `w-[320px]`). Use only project custom design tokens.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/modals/group-study-review-modal.tsx` at line 55,
Replace all Tailwind arbitrary value classes in this component with the
project's custom design tokens: change Modal.Title's class from max-w-[360px] to
max-w-360; replace the element using h-[120px] with the closest permitted height
token (e.g., h-200) or discuss the correct token if none exists; and update the
small avatar/icon block where h-[52px], w-[52px], text-[28px] are used to h-650,
w-650 and a font token like font-designer-28r/m/b respectively (remove the color
`text-` prefix when replacing the font size). Locate these occurrences in
GroupStudyReviewModal (look for Modal.Title and the elements with h-[120px],
h-[52px], w-[52px], text-[28px]) and swap each arbitrary class for the
corresponding custom utility token.

<Modal.Title className="font-designer-16b text-text-default truncate max-w-[360px]">
{detailInfo.title}
</Modal.Title>
<span className="font-designer-13r text-text-subtlest">{`${basicInfo?.startDate} ~ ${basicInfo?.endDate}`}</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

날짜 필드가 비어 있으면 undefined ~ undefined가 노출될 수 있습니다.

Line 58은 optional 체이닝 결과를 그대로 문자열로 렌더링하므로, 값이 없을 때 사용자에게 undefined가 보일 수 있습니다. 기본 문구 fallback을 넣는 편이 안전합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/modals/group-study-review-modal.tsx` at line 58, The
span currently renders `basicInfo?.startDate` and `basicInfo?.endDate` directly,
which can show "undefined ~ undefined"; update the rendering in
group-study-review-modal.tsx to provide fallbacks when those fields are missing
(e.g., use nullish coalescing or conditional rendering for
`basicInfo?.startDate` and `basicInfo?.endDate` and replace undefined with a
user-friendly placeholder like '-' or '미정'), ensuring the displayed string never
contains "undefined".

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (1)
src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx (1)

26-27: ⚠️ Potential issue | 🟠 Major

showToggle=false일 때 항목이 영구히 잘립니다.

Line 26에서 visible 계산이 showToggle을 고려하지 않아, 토글 버튼이 없는 카드도 5개까지만 보입니다.

수정 예시
-  const visible = expanded ? items : items.slice(0, DEFAULT_SHOW_COUNT);
+  const visible =
+    !showToggle || expanded ? items : items.slice(0, DEFAULT_SHOW_COUNT);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx
around lines 26 - 27, The visible calculation wrongly ignores showToggle: update
the logic that sets visible (currently using expanded, items,
DEFAULT_SHOW_COUNT) to respect showToggle so that when showToggle is false all
items are shown; modify the expression that computes visible (and adjust hasMore
if needed) to branch on showToggle (e.g., if !showToggle then visible = items
and hasMore = false, otherwise preserve the existing expanded / slice behavior).
🧹 Nitpick comments (2)
src/hooks/common/use-group-study-review-reminder.ts (2)

73-78: useEffect 의존성 배열에 객체 참조 사용

pendingStudy는 매 렌더링마다 새로 생성되는 객체이므로 참조가 변경됩니다. hasAutoOpenedRef로 보호되어 있어 현재 동작에는 문제가 없지만, pendingStudy?.studyId처럼 primitive 값을 의존성으로 사용하면 불필요한 effect 실행을 방지할 수 있습니다.

🔧 의존성 개선 제안
  useEffect(() => {
    if (pendingStudy && allLoaded && !hasAutoOpenedRef.current) {
      hasAutoOpenedRef.current = true;
      setShowReviewModal(true);
    }
- }, [pendingStudy, allLoaded]);
+ }, [pendingStudy?.studyId, allLoaded]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/common/use-group-study-review-reminder.ts` around lines 73 - 78,
useEffect currently depends on the object pendingStudy which is recreated each
render; change the dependency to a stable primitive (e.g.,
pendingStudy?.studyId) so the effect only runs when the actual study changes:
update the dependency array in the useEffect that checks pendingStudy,
allLoaded, hasAutoOpenedRef and calls setShowReviewModal to use
pendingStudy?.studyId (and allLoaded) instead of pendingStudy to avoid
unnecessary effect runs while keeping the hasAutoOpenedRef protection.

80-89: 타입 단언(type assertion) 대신 명시적 객체 생성 권장

as GroupStudyDetailInfoResponseDto / as GroupStudyBasicInfoResponseDto 단언은 pendingStudy에 DTO에서 요구하는 필드가 모두 있다고 가정하지만, 실제로는 title, startTime, endTime만 사용하고 있습니다. 이 방식은 타입 안전성을 떨어뜨리고, DTO 타입이 변경될 때 런타임 문제가 발생할 수 있습니다.

모달 컴포넌트가 실제로 필요로 하는 필드만 포함하는 별도의 인터페이스를 정의하거나, Partial<T> 또는 Pick<T, K>을 사용하는 것을 권장합니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/common/use-group-study-review-reminder.ts` around lines 80 - 89,
The current use of type assertions on reviewDetailInfo and reviewBasicInfo
(casting to GroupStudyDetailInfoResponseDto / GroupStudyBasicInfoResponseDto) is
unsafe; instead create explicit, narrower shapes using Pick or a local interface
and construct objects with only the fields you actually use from pendingStudy
(e.g., map title, startDate, endDate), or type them as Partial/ Pick<...> to
reflect the subset; update the code that defines reviewDetailInfo and
reviewBasicInfo to build plain objects from pendingStudy without "as" assertions
and reference the new narrower type (or Partial/ Pick) so TypeScript enforces
the correct fields at compile time.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx:
- Around line 59-61: The numeric span currently renders {item.count} which
leaves the area blank when count is undefined; update the rendering in the
evaluation-section component (the span with className "font-designer-14b
shrink-0 tabular-nums" that references item.count inside the mapping over item)
to display a fallback of 0 when item.count is null/undefined (use a
nullish-coalescing fallback like item.count ?? 0 or equivalent) so the UI shows
0 instead of an empty value.
- Line 30: Replace the forbidden Tailwind arbitrary value in the
evaluation-section component by swapping the className token min-h-[280px] with
the project token min-h-280 on the container div (the div with
className="rounded-100 border-border-subtle min-h-[280px] border p-200") in
_components/evaluation-section.tsx so it uses the defined utility instead of an
arbitrary value.

In `@src/components/pages/group-study-list-page.tsx`:
- Line 11: Import 'StudyListToolbar' is failing because the module
'@/components/pages/study-list-toolbar' doesn't exist; either add the missing
StudyListToolbar component file to the PR or update the import in
group-study-list-page.tsx to the correct path or filename used elsewhere (e.g.,
match the path used by premium-study-list-page.tsx or the actual component
name). Locate the import line referencing StudyListToolbar and fix it by adding
the component file to the repo or changing the import specifier to the existing
module export name/path so the module resolves.

In `@src/components/pages/premium-study-list-page.tsx`:
- Line 68: The div uses a Tailwind arbitrary width value (`max-w-[1280px]`)
which violates the guideline; update the JSX where the element has className
"mx-auto w-full max-w-[1280px] px-400 py-600" to replace the arbitrary token
with the design token `max-w-7xl` (i.e., change `max-w-[1280px]` → `max-w-7xl`),
verify there are no other arbitrary Tailwind values in the same component
(search for `[...]` patterns), and run your formatter/type-checker to ensure the
change compiles cleanly.
- Line 11: The import for StudyListToolbar
('@/components/pages/study-list-toolbar') is missing and causes build
failures—create a new component file that exports a default StudyListToolbar
React component (same named export used by premium-study-list-page.tsx and
group-study-list-page.tsx) and implement the expected props/interface used by
those pages so both imports resolve; also replace the arbitrary Tailwind utility
"max-w-[1280px]" in premium-study-list-page.tsx with the design-token class
"max-w-7xl" to conform to project styling.

In `@src/hooks/common/use-group-study-review-reminder.ts`:
- Around line 69-71: The allLoaded check currently only verifies
participantStudies.length and r.data !== undefined, which ignores query error
states; update the allLoaded computation (referencing allLoaded,
participantStudies, and reviewWrittenResults) to also ensure each review query
is not in error (e.g., !r.isError or r.isError === false) or isSuccess before
treating its data as loaded—for example require reviewWrittenResults.every(r =>
r.isSuccess && r.data !== undefined) or reviewWrittenResults.every(r =>
!r.isError && r.data !== undefined) so a failed query doesn't permanently block
the modal opening.

---

Duplicate comments:
In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx:
- Around line 26-27: The visible calculation wrongly ignores showToggle: update
the logic that sets visible (currently using expanded, items,
DEFAULT_SHOW_COUNT) to respect showToggle so that when showToggle is false all
items are shown; modify the expression that computes visible (and adjust hasMore
if needed) to branch on showToggle (e.g., if !showToggle then visible = items
and hasMore = false, otherwise preserve the existing expanded / slice behavior).

---

Nitpick comments:
In `@src/hooks/common/use-group-study-review-reminder.ts`:
- Around line 73-78: useEffect currently depends on the object pendingStudy
which is recreated each render; change the dependency to a stable primitive
(e.g., pendingStudy?.studyId) so the effect only runs when the actual study
changes: update the dependency array in the useEffect that checks pendingStudy,
allLoaded, hasAutoOpenedRef and calls setShowReviewModal to use
pendingStudy?.studyId (and allLoaded) instead of pendingStudy to avoid
unnecessary effect runs while keeping the hasAutoOpenedRef protection.
- Around line 80-89: The current use of type assertions on reviewDetailInfo and
reviewBasicInfo (casting to GroupStudyDetailInfoResponseDto /
GroupStudyBasicInfoResponseDto) is unsafe; instead create explicit, narrower
shapes using Pick or a local interface and construct objects with only the
fields you actually use from pendingStudy (e.g., map title, startDate, endDate),
or type them as Partial/ Pick<...> to reflect the subset; update the code that
defines reviewDetailInfo and reviewBasicInfo to build plain objects from
pendingStudy without "as" assertions and reference the new narrower type (or
Partial/ Pick) so TypeScript enforces the correct fields at compile time.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: bd8d4f73-7502-45d3-a4d3-89683ab8f5df

📥 Commits

Reviewing files that changed from the base of the PR and between dae80cb and 45b0d7c.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (5)
  • package.json
  • src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx
  • src/components/pages/group-study-list-page.tsx
  • src/components/pages/premium-study-list-page.tsx
  • src/hooks/common/use-group-study-review-reminder.ts
✅ Files skipped from review due to trivial changes (1)
  • package.json

const hasMore = items.length > DEFAULT_SHOW_COUNT;

return (
<div className="rounded-100 border-border-subtle min-h-[280px] border p-200">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n "src/app/(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx" | sed -n '25,35p'

Repository: code-zero-to-one/study-platform-client

Length of output: 590


🏁 Script executed:

cat "src/app/global.css" | grep -E "(--height|--min-height|280|1120)" | head -20

Repository: code-zero-to-one/study-platform-client

Length of output: 175


🏁 Script executed:

# Search for any existing uses of min-height in the codebase and their token values
rg "min-h-\[" --type tsx --type ts -A 2 -B 2 | head -50

Repository: code-zero-to-one/study-platform-client

Length of output: 109


🏁 Script executed:

# Check if there are predefined height tokens in the project
rg "min-h-" "src/app/global.css" -A 1 -B 1

Repository: code-zero-to-one/study-platform-client

Length of output: 216


min-h-[280px]를 프로젝트 토큰 min-h-280으로 교체하세요.

금지된 Tailwind arbitrary value를 사용 중입니다. 프로젝트에서 정의한 min-h-280 유틸리티를 사용해 주세요.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx
at line 30, Replace the forbidden Tailwind arbitrary value in the
evaluation-section component by swapping the className token min-h-[280px] with
the project token min-h-280 on the container div (the div with
className="rounded-100 border-border-subtle min-h-[280px] border p-200") in
_components/evaluation-section.tsx so it uses the defined utility instead of an
arbitrary value.

Comment on lines +59 to +61
<span className="font-designer-14b shrink-0 tabular-nums">
{item.count}
</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

count가 없을 때 빈 값 대신 0을 렌더링하세요.

Line 60은 item.count가 없으면 숫자 영역이 비어 보일 수 있습니다.

수정 예시
-              <span className="font-designer-14b shrink-0 tabular-nums">
-                {item.count}
-              </span>
+              <span className="font-designer-14b shrink-0 tabular-nums">
+                {item.count ?? 0}
+              </span>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<span className="font-designer-14b shrink-0 tabular-nums">
{item.count}
</span>
<span className="font-designer-14b shrink-0 tabular-nums">
{item.count ?? 0}
</span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/app/`(service)/(my)/my-study-review/group/[groupStudyId]/_components/evaluation-section.tsx
around lines 59 - 61, The numeric span currently renders {item.count} which
leaves the area blank when count is undefined; update the rendering in the
evaluation-section component (the span with className "font-designer-14b
shrink-0 tabular-nums" that references item.count inside the mapping over item)
to display a fallback of 0 when item.count is null/undefined (use a
nullish-coalescing fallback like item.count ?? 0 or equivalent) so the UI shows
0 instead of an empty value.

import { useStudyListFilter } from '@/hooks/common/use-study-list-filter';
import GroupStudyList from '../lists/group-study-list';
import MyParticipatingStudiesSection from '../section/my-participating-studies-section';
import StudyListToolbar from '@/components/pages/study-list-toolbar';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

빌드 실패: 존재하지 않는 모듈 import

premium-study-list-page.tsx와 동일하게 @/components/pages/study-list-toolbar 모듈을 찾을 수 없습니다. PR에 StudyListToolbar 컴포넌트 파일을 포함시키거나 경로를 수정해 주세요.

🧰 Tools
🪛 GitHub Check: typecheck

[failure] 11-11:
Cannot find module '@/components/pages/study-list-toolbar' or its corresponding type declarations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/pages/group-study-list-page.tsx` at line 11, Import
'StudyListToolbar' is failing because the module
'@/components/pages/study-list-toolbar' doesn't exist; either add the missing
StudyListToolbar component file to the PR or update the import in
group-study-list-page.tsx to the correct path or filename used elsewhere (e.g.,
match the path used by premium-study-list-page.tsx or the actual component
name). Locate the import line referencing StudyListToolbar and fix it by adding
the component file to the repo or changing the import specifier to the existing
module export name/path so the module resolves.

import { useGroupStudyReviewReminder } from '@/hooks/common/use-group-study-review-reminder';
import { useStudyListFilter } from '@/hooks/common/use-study-list-filter';
import MyParticipatingStudiesSection from '../section/my-participating-studies-section';
import StudyListToolbar from '@/components/pages/study-list-toolbar';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: StudyListToolbar 파일 존재 여부 확인
# Expected: 파일이 존재해야 함

fd -t f "study-list-toolbar" --extension ts --extension tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 1811


🏁 Script executed:

#!/bin/bash
# StudyListToolbar 파일 검색
# 모든 가능한 파일명 패턴 확인

find . -type f \( -name "*study-list-toolbar*" -o -name "*study_list_toolbar*" -o -name "*studyListToolbar*" \) 2>/dev/null | head -20

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

#!/bin/bash
# 컴포넌트 디렉토리 구조 확인
ls -la src/components/pages/ 2>/dev/null || echo "디렉토리 없음"

Repository: code-zero-to-one/study-platform-client

Length of output: 794


🏁 Script executed:

#!/bin/bash
# 유사한 이름의 파일 검색
find src/components -type f \( -name "*.tsx" -o -name "*.ts" \) 2>/dev/null | grep -i "toolbar\|study" | head -20

Repository: code-zero-to-one/study-platform-client

Length of output: 1123


🏁 Script executed:

#!/bin/bash
# premium-study-list-page.tsx 파일 내용 확인
head -50 src/components/pages/premium-study-list-page.tsx | cat -n

Repository: code-zero-to-one/study-platform-client

Length of output: 1991


🏁 Script executed:

#!/bin/bash
# study-list-toolbar 유사 파일 찾기
find src -type f \( -name "*.tsx" -o -name "*.ts" \) | xargs grep -l "StudyListToolbar" 2>/dev/null

Repository: code-zero-to-one/study-platform-client

Length of output: 177


🏁 Script executed:

#!/bin/bash
# toolbar 관련 모든 파일 검색
find src/components -type f \( -name "*toolbar*" \) 2>/dev/null

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

#!/bin/bash
# StudyListToolbar 컴포넌트가 export되는 곳 찾기
rg "export.*StudyListToolbar" --type ts --type tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 109


🏁 Script executed:

#!/bin/bash
# group-study-list-page.tsx에서 어떻게 import하는지 확인
grep -n "StudyListToolbar" src/components/pages/group-study-list-page.tsx

Repository: code-zero-to-one/study-platform-client

Length of output: 181


🏁 Script executed:

#!/bin/bash
# study-list-toolbar가 정말 없는지 전체 저장소 확인
find . -name "study-list-toolbar*" -o -name "*StudyListToolbar*" 2>/dev/null

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

#!/bin/bash
# group-study-list-page.tsx 파일 확인 - StudyListToolbar 사용하는지 확인
head -100 src/components/pages/group-study-list-page.tsx | cat -n

Repository: code-zero-to-one/study-platform-client

Length of output: 3707


🏁 Script executed:

#!/bin/bash
# pages 디렉토리의 모든 파일 나열
ls -la src/components/pages/

Repository: code-zero-to-one/study-platform-client

Length of output: 794


🏁 Script executed:

#!/bin/bash
# 더 넓은 범위로 toolbar 검색
find src -type f \( -name "*.tsx" -o -name "*.ts" \) -exec grep -l "StudyListToolbar" {} \;

Repository: code-zero-to-one/study-platform-client

Length of output: 177


🏁 Script executed:

#!/bin/bash
# section 디렉토리 확인 (import 경로에 ../section이 있음)
ls -la src/components/section/ 2>/dev/null || echo "section 디렉토리 확인"

Repository: code-zero-to-one/study-platform-client

Length of output: 671


🏁 Script executed:

#!/bin/bash
# premium-study-list-page.tsx 전체 내용 확인 (특히 line 68 주변)
cat -n src/components/pages/premium-study-list-page.tsx | sed -n '60,80p'

Repository: code-zero-to-one/study-platform-client

Length of output: 974


🏁 Script executed:

#!/bin/bash
# components 구조 확인 - 어디에 StudyListToolbar가 있어야 하는지
find src/components -type d | head -30

Repository: code-zero-to-one/study-platform-client

Length of output: 983


🏁 Script executed:

#!/bin/bash
# section 디렉토리에서 StudyListToolbar 검색
grep -r "StudyListToolbar" src/components/section/ 2>/dev/null || echo "section에 없음"

Repository: code-zero-to-one/study-platform-client

Length of output: 93


🏁 Script executed:

#!/bin/bash
# 전체 components에서 StudyListToolbar 정의 찾기
find src/components -type f \( -name "*.tsx" -o -name "*.ts" \) -exec grep -l "export.*StudyListToolbar\|export default.*StudyListToolbar" {} \;

Repository: code-zero-to-one/study-platform-client

Length of output: 64


🏁 Script executed:

#!/bin/bash
# 혹시 다른 이름으로 정의되었을 가능성
find src/components -type f \( -name "*.tsx" -o -name "*.ts" \) -exec grep -l "class StudyListToolbar\|function StudyListToolbar\|const StudyListToolbar" {} \;

Repository: code-zero-to-one/study-platform-client

Length of output: 64


필수 파일 누락: StudyListToolbar 컴포넌트가 존재하지 않음

@/components/pages/study-list-toolbar 모듈이 저장소에 없어서 빌드가 실패합니다. 이 컴포넌트는 group-study-list-page.tsx와 premium-study-list-page.tsx 두 페이지 모두에서 import되어 사용 중입니다.

또한 line 68의 max-w-[1280px]는 임의의 Tailwind 값 사용이므로 프로젝트 디자인 토큰인 max-w-7xl로 변경해야 합니다.

🧰 Tools
🪛 GitHub Check: typecheck

[failure] 11-11:
Cannot find module '@/components/pages/study-list-toolbar' or its corresponding type declarations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/pages/premium-study-list-page.tsx` at line 11, The import for
StudyListToolbar ('@/components/pages/study-list-toolbar') is missing and causes
build failures—create a new component file that exports a default
StudyListToolbar React component (same named export used by
premium-study-list-page.tsx and group-study-list-page.tsx) and implement the
expected props/interface used by those pages so both imports resolve; also
replace the arbitrary Tailwind utility "max-w-[1280px]" in
premium-study-list-page.tsx with the design-token class "max-w-7xl" to conform
to project styling.


return (
<div className="mx-auto w-[1280px] px-400 py-600">
<div className="mx-auto w-full max-w-[1280px] px-400 py-600">

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tailwind arbitrary value 대신 디자인 토큰 사용

max-w-[1280px]는 코딩 가이드라인에서 금지된 임의 값(arbitrary value)입니다. 프로젝트에서 w-7xl이 1280px에 해당하는 유효한 토큰이므로 max-w-7xl을 사용해야 합니다.

As per coding guidelines: "Forbidden: Tailwind arbitrary values (e.g., p-[4px], w-[320px]). Use only project custom design tokens."

🔧 디자인 토큰으로 수정
- <div className="mx-auto w-full max-w-[1280px] px-400 py-600">
+ <div className="mx-auto w-full max-w-7xl px-400 py-600">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="mx-auto w-full max-w-[1280px] px-400 py-600">
<div className="mx-auto w-full max-w-7xl px-400 py-600">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/pages/premium-study-list-page.tsx` at line 68, The div uses a
Tailwind arbitrary width value (`max-w-[1280px]`) which violates the guideline;
update the JSX where the element has className "mx-auto w-full max-w-[1280px]
px-400 py-600" to replace the arbitrary token with the design token `max-w-7xl`
(i.e., change `max-w-[1280px]` → `max-w-7xl`), verify there are no other
arbitrary Tailwind values in the same component (search for `[...]` patterns),
and run your formatter/type-checker to ensure the change compiles cleanly.

Comment on lines +69 to +71
const allLoaded =
participantStudies.length > 0 &&
reviewWrittenResults.every((r) => r.data !== undefined);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

allLoaded 체크가 쿼리 에러 상태를 고려하지 않음

r.data !== undefined만 확인하면 쿼리가 에러로 실패한 경우에도 dataundefined이므로 allLoaded가 영원히 false가 됩니다. 이로 인해 하나의 스터디 리뷰 작성 여부 조회가 실패하면 모달이 절대 열리지 않는 문제가 발생할 수 있습니다.

🛠️ 에러 상태를 포함한 수정 제안
  const allLoaded =
    participantStudies.length > 0 &&
-   reviewWrittenResults.every((r) => r.data !== undefined);
+   reviewWrittenResults.every((r) => r.isSuccess || r.isError);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const allLoaded =
participantStudies.length > 0 &&
reviewWrittenResults.every((r) => r.data !== undefined);
const allLoaded =
participantStudies.length > 0 &&
reviewWrittenResults.every((r) => r.isSuccess || r.isError);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/common/use-group-study-review-reminder.ts` around lines 69 - 71,
The allLoaded check currently only verifies participantStudies.length and r.data
!== undefined, which ignores query error states; update the allLoaded
computation (referencing allLoaded, participantStudies, and
reviewWrittenResults) to also ensure each review query is not in error (e.g.,
!r.isError or r.isError === false) or isSuccess before treating its data as
loaded—for example require reviewWrittenResults.every(r => r.isSuccess && r.data
!== undefined) or reviewWrittenResults.every(r => !r.isError && r.data !==
undefined) so a failed query doesn't permanently block the modal opening.

@HA-SEUNG-JEONG HA-SEUNG-JEONG merged commit 06e0f8f into main Mar 24, 2026
7 of 9 checks passed
@HA-SEUNG-JEONG HA-SEUNG-JEONG deleted the haseung/fix-study branch March 24, 2026 13:27
@coderabbitai coderabbitai Bot mentioned this pull request Mar 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant